Change build scripts
[jvcard.git] / src / com / googlecode / lanterna / terminal / ansi / ANSITerminal.java
1 /*
2 * This file is part of lanterna (http://code.google.com/p/lanterna/).
3 *
4 * lanterna is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Copyright (C) 2010-2015 Martin
18 */
19 package com.googlecode.lanterna.terminal.ansi;
20
21 import com.googlecode.lanterna.SGR;
22 import com.googlecode.lanterna.input.*;
23 import com.googlecode.lanterna.TerminalSize;
24 import com.googlecode.lanterna.TextColor;
25 import com.googlecode.lanterna.terminal.ExtendedTerminal;
26 import com.googlecode.lanterna.terminal.MouseCaptureMode;
27
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.nio.charset.Charset;
32
33 /**
34 * Class containing graphics code for ANSI compliant text terminals and terminal emulators. All the methods inside of
35 * this class uses ANSI escape codes written to the underlying output stream.
36 *
37 * @see <a href="http://en.wikipedia.org/wiki/ANSI_escape_code">Wikipedia</a>
38 * @author Martin
39 */
40 public abstract class ANSITerminal extends StreamBasedTerminal implements ExtendedTerminal {
41
42 private MouseCaptureMode mouseCaptureMode;
43 private boolean inPrivateMode;
44
45 @SuppressWarnings("WeakerAccess")
46 protected ANSITerminal(InputStream terminalInput, OutputStream terminalOutput, Charset terminalCharset) {
47 super(terminalInput, terminalOutput, terminalCharset);
48 this.inPrivateMode = false;
49 this.mouseCaptureMode = null;
50 getInputDecoder().addProfile(getDefaultKeyDecodingProfile());
51 }
52
53 /**
54 * This method can be overridden in a custom terminal implementation to change the default key decoders.
55 * @return The KeyDecodingProfile used by the terminal when translating character sequences to keystrokes
56 */
57 protected KeyDecodingProfile getDefaultKeyDecodingProfile() {
58 return new DefaultKeyDecodingProfile();
59 }
60
61 private void writeCSISequenceToTerminal(byte... tail) throws IOException {
62 byte[] completeSequence = new byte[tail.length + 2];
63 completeSequence[0] = (byte)0x1b;
64 completeSequence[1] = (byte)'[';
65 System.arraycopy(tail, 0, completeSequence, 2, tail.length);
66 writeToTerminal(completeSequence);
67 }
68
69 private void writeSGRSequenceToTerminal(byte... sgrParameters) throws IOException {
70 byte[] completeSequence = new byte[sgrParameters.length + 3];
71 completeSequence[0] = (byte)0x1b;
72 completeSequence[1] = (byte)'[';
73 completeSequence[completeSequence.length - 1] = (byte)'m';
74 System.arraycopy(sgrParameters, 0, completeSequence, 2, sgrParameters.length);
75 writeToTerminal(completeSequence);
76 }
77
78 private void writeOSCSequenceToTerminal(byte... tail) throws IOException {
79 byte[] completeSequence = new byte[tail.length + 2];
80 completeSequence[0] = (byte)0x1b;
81 completeSequence[1] = (byte)']';
82 System.arraycopy(tail, 0, completeSequence, 2, tail.length);
83 writeToTerminal(completeSequence);
84 }
85
86 @Override
87 public TerminalSize getTerminalSize() throws IOException {
88 saveCursorPosition();
89 setCursorPosition(5000, 5000);
90 reportPosition();
91 restoreCursorPosition();
92 return waitForTerminalSizeReport();
93 }
94
95 @Override
96 public void setTerminalSize(int columns, int rows) throws IOException {
97 writeCSISequenceToTerminal(("8;" + rows + ";" + columns + "t").getBytes());
98
99 //We can't trust that the previous call was honoured by the terminal so force a re-query here, which will
100 //trigger a resize event if one actually took place
101 getTerminalSize();
102 }
103
104 @Override
105 public void setTitle(String title) throws IOException {
106 //The bell character is our 'null terminator', make sure there's none in the title
107 title = title.replace("\007", "");
108 writeOSCSequenceToTerminal(("2;" + title + "\007").getBytes());
109 }
110
111 @Override
112 public void setForegroundColor(TextColor color) throws IOException {
113 writeSGRSequenceToTerminal(color.getForegroundSGRSequence());
114 }
115
116 @Override
117 public void setBackgroundColor(TextColor color) throws IOException {
118 writeSGRSequenceToTerminal(color.getBackgroundSGRSequence());
119 }
120
121 @Override
122 public void enableSGR(SGR sgr) throws IOException {
123 switch(sgr) {
124 case BLINK:
125 writeCSISequenceToTerminal((byte) '5', (byte) 'm');
126 break;
127 case BOLD:
128 writeCSISequenceToTerminal((byte) '1', (byte) 'm');
129 break;
130 case BORDERED:
131 writeCSISequenceToTerminal((byte) '5', (byte) '1', (byte) 'm');
132 break;
133 case CIRCLED:
134 writeCSISequenceToTerminal((byte) '5', (byte) '2', (byte) 'm');
135 break;
136 case CROSSED_OUT:
137 writeCSISequenceToTerminal((byte) '9', (byte) 'm');
138 break;
139 case FRAKTUR:
140 writeCSISequenceToTerminal((byte) '2', (byte) '0', (byte) 'm');
141 break;
142 case REVERSE:
143 writeCSISequenceToTerminal((byte) '7', (byte) 'm');
144 break;
145 case UNDERLINE:
146 writeCSISequenceToTerminal((byte) '4', (byte) 'm');
147 break;
148 }
149 }
150
151 @Override
152 public void disableSGR(SGR sgr) throws IOException {
153 switch(sgr) {
154 case BLINK:
155 writeCSISequenceToTerminal((byte) '2', (byte) '5', (byte) 'm');
156 break;
157 case BOLD:
158 writeCSISequenceToTerminal((byte) '2', (byte) '2', (byte) 'm');
159 break;
160 case BORDERED:
161 writeCSISequenceToTerminal((byte) '5', (byte) '4', (byte) 'm');
162 break;
163 case CIRCLED:
164 writeCSISequenceToTerminal((byte) '5', (byte) '4', (byte) 'm');
165 break;
166 case CROSSED_OUT:
167 writeCSISequenceToTerminal((byte) '2', (byte) '9', (byte) 'm');
168 break;
169 case FRAKTUR:
170 writeCSISequenceToTerminal((byte) '2', (byte) '3', (byte) 'm');
171 break;
172 case REVERSE:
173 writeCSISequenceToTerminal((byte) '2', (byte) '7', (byte) 'm');
174 break;
175 case UNDERLINE:
176 writeCSISequenceToTerminal((byte) '2', (byte) '4', (byte) 'm');
177 break;
178 }
179 }
180
181 @Override
182 public void resetColorAndSGR() throws IOException {
183 writeCSISequenceToTerminal((byte) '0', (byte) 'm');
184 }
185
186 @Override
187 public void clearScreen() throws IOException {
188 writeCSISequenceToTerminal((byte) '2', (byte) 'J');
189 }
190
191 @Override
192 public void enterPrivateMode() throws IOException {
193 if(inPrivateMode) {
194 throw new IllegalStateException("Cannot call enterPrivateMode() when already in private mode");
195 }
196 writeCSISequenceToTerminal((byte) '?', (byte) '1', (byte) '0', (byte) '4', (byte) '9', (byte) 'h');
197 inPrivateMode = true;
198 }
199
200 @Override
201 public void exitPrivateMode() throws IOException {
202 if(!inPrivateMode) {
203 throw new IllegalStateException("Cannot call exitPrivateMode() when not in private mode");
204 }
205 resetColorAndSGR();
206 setCursorVisible(true);
207 writeCSISequenceToTerminal((byte) '?', (byte) '1', (byte) '0', (byte) '4', (byte) '9', (byte) 'l');
208 inPrivateMode = false;
209 }
210
211 @Override
212 public void setCursorPosition(int x, int y) throws IOException {
213 writeCSISequenceToTerminal(((y + 1) + ";" + (x + 1) + "H").getBytes());
214 }
215
216 @Override
217 public void setCursorVisible(boolean visible) throws IOException {
218 writeCSISequenceToTerminal(("?25" + (visible ? "h" : "l")).getBytes());
219 }
220
221 @Override
222 public KeyStroke readInput() throws IOException {
223 KeyStroke keyStroke;
224 do {
225 keyStroke = filterMouseEvents(super.readInput());
226 } while(keyStroke == null);
227 return keyStroke;
228 }
229
230 @Override
231 public KeyStroke pollInput() throws IOException {
232 return filterMouseEvents(super.pollInput());
233 }
234
235 private KeyStroke filterMouseEvents(KeyStroke keyStroke) {
236 //Remove bad input events from terminals that are not following the xterm protocol properly
237 if(keyStroke == null || keyStroke.getKeyType() != KeyType.MouseEvent) {
238 return keyStroke;
239 }
240
241 MouseAction mouseAction = (MouseAction)keyStroke;
242 switch(mouseAction.getActionType()) {
243 case CLICK_RELEASE:
244 if(mouseCaptureMode == MouseCaptureMode.CLICK) {
245 return null;
246 }
247 break;
248 case DRAG:
249 if(mouseCaptureMode == MouseCaptureMode.CLICK ||
250 mouseCaptureMode == MouseCaptureMode.CLICK_RELEASE) {
251 return null;
252 }
253 break;
254 case MOVE:
255 if(mouseCaptureMode == MouseCaptureMode.CLICK ||
256 mouseCaptureMode == MouseCaptureMode.CLICK_RELEASE ||
257 mouseCaptureMode == MouseCaptureMode.CLICK_RELEASE_DRAG) {
258 return null;
259 }
260 break;
261 default:
262 }
263 return mouseAction;
264 }
265
266 @Override
267 public void pushTitle() throws IOException {
268 throw new UnsupportedOperationException("Not implemented yet");
269 }
270
271 @Override
272 public void popTitle() throws IOException {
273 throw new UnsupportedOperationException("Not implemented yet");
274 }
275
276 @Override
277 public void iconify() throws IOException {
278 writeCSISequenceToTerminal((byte)'2', (byte)'t');
279 }
280
281 @Override
282 public void deiconify() throws IOException {
283 writeCSISequenceToTerminal((byte)'1', (byte)'t');
284 }
285
286 @Override
287 public void maximize() throws IOException {
288 writeCSISequenceToTerminal((byte)'9', (byte)';', (byte)'1', (byte)'t');
289 }
290
291 @Override
292 public void unmaximize() throws IOException {
293 writeCSISequenceToTerminal((byte)'9', (byte)';', (byte)'0', (byte)'t');
294 }
295
296 @Override
297 public void setMouseCaptureMode(MouseCaptureMode mouseCaptureMode) throws IOException {
298 if(this.mouseCaptureMode != null) {
299 switch(this.mouseCaptureMode) {
300 case CLICK:
301 writeCSISequenceToTerminal((byte)'?', (byte)'9', (byte)'l');
302 break;
303 case CLICK_RELEASE:
304 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'0', (byte)'l');
305 break;
306 case CLICK_RELEASE_DRAG:
307 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'2', (byte)'l');
308 break;
309 case CLICK_RELEASE_DRAG_MOVE:
310 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'3', (byte)'l');
311 break;
312 }
313 if(getCharset().equals(Charset.forName("UTF-8"))) {
314 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'5', (byte)'l');
315 }
316 }
317 this.mouseCaptureMode = mouseCaptureMode;
318 if(this.mouseCaptureMode != null) {
319 switch(this.mouseCaptureMode) {
320 case CLICK:
321 writeCSISequenceToTerminal((byte)'?', (byte)'9', (byte)'h');
322 break;
323 case CLICK_RELEASE:
324 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'0', (byte)'h');
325 break;
326 case CLICK_RELEASE_DRAG:
327 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'2', (byte)'h');
328 break;
329 case CLICK_RELEASE_DRAG_MOVE:
330 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'3', (byte)'h');
331 break;
332 }
333 if(getCharset().equals(Charset.forName("UTF-8"))) {
334 writeCSISequenceToTerminal((byte)'?', (byte)'1', (byte)'0', (byte)'0', (byte)'5', (byte)'h');
335 }
336 }
337 }
338
339 /**
340 * Method to test if the terminal (as far as the library knows) is in private mode.
341 *
342 * @return True if there has been a call to enterPrivateMode() but not yet exitPrivateMode()
343 */
344 boolean isInPrivateMode() {
345 return inPrivateMode;
346 }
347
348 void reportPosition() throws IOException {
349 writeCSISequenceToTerminal("6n".getBytes());
350 }
351
352 void restoreCursorPosition() throws IOException {
353 writeCSISequenceToTerminal("u".getBytes());
354 }
355
356 void saveCursorPosition() throws IOException {
357 writeCSISequenceToTerminal("s".getBytes());
358 }
359
360 @Override
361 public void scrollLines(int firstLine, int lastLine, int distance) throws IOException {
362 final String CSI = "\033[";
363
364 // some sanity checks:
365 if (distance == 0) { return; }
366 if (firstLine < 0) { firstLine = 0; }
367 if (lastLine < firstLine) { return; }
368 StringBuilder sb = new StringBuilder();
369
370 // define range:
371 sb.append(CSI).append(firstLine+1)
372 .append(';').append(lastLine+1).append('r');
373
374 // place cursor on line to scroll away from:
375 int target = distance > 0 ? lastLine : firstLine;
376 sb.append(CSI).append(target+1).append(";1H");
377
378 // do scroll:
379 if (distance > 0) {
380 int num = Math.min( distance, lastLine - firstLine + 1);
381 for (int i = 0; i < num; i++) { sb.append('\n'); }
382 } else { // distance < 0
383 int num = Math.min( -distance, lastLine - firstLine + 1);
384 for (int i = 0; i < num; i++) { sb.append("\033M"); }
385 }
386
387 // reset range:
388 sb.append(CSI).append('r');
389
390 // off we go!
391 writeToTerminal(sb.toString().getBytes());
392 }
393 }