Commit | Line | Data |
---|---|---|
217c6107 KL |
1 | /** |
2 | * Jexer - Java Text User Interface | |
3 | * | |
217c6107 KL |
4 | * License: LGPLv3 or later |
5 | * | |
7b5261bc KL |
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. | |
217c6107 KL |
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 | |
7b5261bc KL |
27 | * |
28 | * @author Kevin Lamonte [kevin.lamonte@gmail.com] | |
29 | * @version 1 | |
217c6107 KL |
30 | */ |
31 | package jexer.io; | |
32 | ||
33 | import jexer.bits.Cell; | |
34 | import jexer.bits.CellAttributes; | |
35 | ||
36 | /** | |
2b9c27db KL |
37 | * This Screen implementation draws to an xterm/ANSI X3.64/ECMA-48 type |
38 | * terminal. | |
217c6107 | 39 | */ |
2b9c27db | 40 | public final class ECMA48Screen extends Screen { |
217c6107 KL |
41 | |
42 | /** | |
7b5261bc KL |
43 | * Emit debugging to stderr. |
44 | */ | |
45 | private boolean debugToStderr; | |
46 | ||
47 | /** | |
48 | * We call terminal.cursor() so need the instance. | |
217c6107 KL |
49 | */ |
50 | private ECMA48Terminal terminal; | |
51 | ||
52 | /** | |
7b5261bc | 53 | * Public constructor. |
217c6107 KL |
54 | * |
55 | * @param terminal ECMA48Terminal to use | |
56 | */ | |
7b5261bc KL |
57 | public ECMA48Screen(final ECMA48Terminal terminal) { |
58 | debugToStderr = false; | |
59 | ||
60 | this.terminal = terminal; | |
217c6107 | 61 | |
7b5261bc KL |
62 | // Query the screen size |
63 | setDimensions(terminal.getSessionInfo().getWindowWidth(), | |
64 | terminal.getSessionInfo().getWindowHeight()); | |
217c6107 KL |
65 | } |
66 | ||
67 | /** | |
7b5261bc | 68 | * Perform a somewhat-optimal rendering of a line. |
217c6107 KL |
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 | */ | |
7b5261bc KL |
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 | ||
8dc20d38 | 95 | if (!lCell.equals(pCell) || reallyCleared) { |
7b5261bc KL |
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.getBold() == lastAttr.getBold()) | |
137 | && (lCell.getReverse() == lastAttr.getReverse()) | |
138 | && (lCell.getUnderline() == lastAttr.getUnderline()) | |
139 | && (lCell.getBlink() == lastAttr.getBlink()) | |
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.getBold() != lastAttr.getBold()) | |
151 | && (lCell.getReverse() != lastAttr.getReverse()) | |
152 | && (lCell.getUnderline() != lastAttr.getUnderline()) | |
153 | && (lCell.getBlink() != lastAttr.getBlink()) | |
154 | ) { | |
155 | // Everything is different | |
156 | sb.append(terminal.color(lCell.getForeColor(), | |
157 | lCell.getBackColor(), | |
158 | lCell.getBold(), lCell.getReverse(), | |
159 | lCell.getBlink(), | |
160 | lCell.getUnderline())); | |
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.getBold() == lastAttr.getBold()) | |
168 | && (lCell.getReverse() == lastAttr.getReverse()) | |
169 | && (lCell.getUnderline() == lastAttr.getUnderline()) | |
170 | && (lCell.getBlink() == lastAttr.getBlink()) | |
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.getBold() == lastAttr.getBold()) | |
182 | && (lCell.getReverse() == lastAttr.getReverse()) | |
183 | && (lCell.getUnderline() == lastAttr.getUnderline()) | |
184 | && (lCell.getBlink() == lastAttr.getBlink()) | |
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.getBold() == lastAttr.getBold()) | |
195 | && (lCell.getReverse() == lastAttr.getReverse()) | |
196 | && (lCell.getUnderline() == lastAttr.getUnderline()) | |
197 | && (lCell.getBlink() == lastAttr.getBlink()) | |
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.getBold(), | |
211 | lCell.getReverse(), | |
212 | lCell.getBlink(), | |
213 | lCell.getUnderline())); | |
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 | ||
8dc20d38 | 226 | // Physical is always updated |
7b5261bc KL |
227 | physical[x][y].setTo(lCell); |
228 | ||
8dc20d38 | 229 | } // if (!lCell.equals(pCell) || (reallyCleared == true)) |
7b5261bc KL |
230 | |
231 | } // for (int x = 0; x < width; x++) | |
217c6107 KL |
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 | */ | |
1ac2ccb1 | 241 | private String flushString() { |
7b5261bc KL |
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; | |
217c6107 KL |
267 | } |
268 | ||
269 | /** | |
270 | * Push the logical screen to the physical device. | |
271 | */ | |
272 | @Override | |
273 | public void flushPhysical() { | |
7b5261bc KL |
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(); | |
217c6107 KL |
286 | } |
287 | } |