Merge branch 'subtree'
[fanfix.git] / src / jexer / TTerminalWindow.java
1 /*
2 * Jexer - Java Text User Interface
3 *
4 * The MIT License (MIT)
5 *
6 * Copyright (C) 2019 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;
30
31 import java.util.ResourceBundle;
32
33 import jexer.menu.TMenu;
34 import jexer.event.TKeypressEvent;
35 import jexer.event.TMenuEvent;
36 import jexer.event.TMouseEvent;
37 import jexer.event.TResizeEvent;
38 import static jexer.TCommand.*;
39 import static jexer.TKeypress.*;
40
41 /**
42 * TTerminalWindow exposes a ECMA-48 / ANSI X3.64 style terminal in a window.
43 */
44 public class TTerminalWindow extends TScrollableWindow {
45
46 /**
47 * Translated strings.
48 */
49 private static final ResourceBundle i18n = ResourceBundle.getBundle(TTerminalWindow.class.getName());
50
51 // ------------------------------------------------------------------------
52 // Variables --------------------------------------------------------------
53 // ------------------------------------------------------------------------
54
55 /**
56 * The terminal.
57 */
58 private TTerminalWidget terminal;
59
60 /**
61 * If true, close the window when the shell exits.
62 */
63 private boolean closeOnExit = false;
64
65 // ------------------------------------------------------------------------
66 // Constructors -----------------------------------------------------------
67 // ------------------------------------------------------------------------
68
69 /**
70 * Public constructor spawns a custom command line.
71 *
72 * @param application TApplication that manages this window
73 * @param x column relative to parent
74 * @param y row relative to parent
75 * @param commandLine the command line to execute
76 */
77 public TTerminalWindow(final TApplication application, final int x,
78 final int y, final String commandLine) {
79
80 this(application, x, y, RESIZABLE, commandLine.split("\\s+"),
81 System.getProperty("jexer.TTerminal.closeOnExit",
82 "false").equals("true"));
83 }
84
85 /**
86 * Public constructor spawns a custom command line.
87 *
88 * @param application TApplication that manages this window
89 * @param x column relative to parent
90 * @param y row relative to parent
91 * @param commandLine the command line to execute
92 * @param closeOnExit if true, close the window when the command exits
93 */
94 public TTerminalWindow(final TApplication application, final int x,
95 final int y, final String commandLine, final boolean closeOnExit) {
96
97 this(application, x, y, RESIZABLE, commandLine.split("\\s+"),
98 closeOnExit);
99 }
100
101 /**
102 * Public constructor spawns a custom command line.
103 *
104 * @param application TApplication that manages this window
105 * @param x column relative to parent
106 * @param y row relative to parent
107 * @param flags mask of CENTERED, MODAL, or RESIZABLE
108 * @param command the command line to execute
109 */
110 public TTerminalWindow(final TApplication application, final int x,
111 final int y, final int flags, final String [] command) {
112
113 this(application, x, y, flags, command,
114 System.getProperty("jexer.TTerminal.closeOnExit",
115 "false").equals("true"));
116 }
117
118 /**
119 * Public constructor spawns a custom command line.
120 *
121 * @param application TApplication that manages this window
122 * @param x column relative to parent
123 * @param y row relative to parent
124 * @param flags mask of CENTERED, MODAL, or RESIZABLE
125 * @param command the command line to execute
126 * @param closeOnExit if true, close the window when the command exits
127 */
128 public TTerminalWindow(final TApplication application, final int x,
129 final int y, final int flags, final String [] command,
130 final boolean closeOnExit) {
131
132 super(application, i18n.getString("windowTitle"), x, y,
133 80 + 2, 24 + 2, flags);
134
135 // Require at least one line for the display.
136 setMinimumWindowHeight(3);
137
138 this.closeOnExit = closeOnExit;
139 vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
140
141 // Claim the keystrokes the emulator will need.
142 addShortcutKeys();
143
144 // Add shortcut text
145 TStatusBar statusBar = newStatusBar(i18n.getString("statusBarRunning"));
146 statusBar.addShortcutKeypress(kbF1, cmHelp,
147 i18n.getString("statusBarHelp"));
148 statusBar.addShortcutKeypress(kbF10, cmMenu,
149 i18n.getString("statusBarMenu"));
150
151 // Spin it up
152 terminal = new TTerminalWidget(this, 0, 0, command, new TAction() {
153 public void DO() {
154 onShellExit();
155 }
156 });
157 }
158
159 /**
160 * Public constructor spawns a shell.
161 *
162 * @param application TApplication that manages this window
163 * @param x column relative to parent
164 * @param y row relative to parent
165 * @param flags mask of CENTERED, MODAL, or RESIZABLE
166 */
167 public TTerminalWindow(final TApplication application, final int x,
168 final int y, final int flags) {
169
170 this(application, x, y, flags,
171 System.getProperty("jexer.TTerminal.closeOnExit",
172 "false").equals("true"));
173
174 }
175
176 /**
177 * Public constructor spawns a shell.
178 *
179 * @param application TApplication that manages this window
180 * @param x column relative to parent
181 * @param y row relative to parent
182 * @param flags mask of CENTERED, MODAL, or RESIZABLE
183 * @param closeOnExit if true, close the window when the shell exits
184 */
185 public TTerminalWindow(final TApplication application, final int x,
186 final int y, final int flags, final boolean closeOnExit) {
187
188 super(application, i18n.getString("windowTitle"), x, y,
189 80 + 2, 24 + 2, flags);
190
191 // Require at least one line for the display.
192 setMinimumWindowHeight(3);
193
194 this.closeOnExit = closeOnExit;
195 vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2);
196
197 // Claim the keystrokes the emulator will need.
198 addShortcutKeys();
199
200 // Add shortcut text
201 TStatusBar statusBar = newStatusBar(i18n.getString("statusBarRunning"));
202 statusBar.addShortcutKeypress(kbF1, cmHelp,
203 i18n.getString("statusBarHelp"));
204 statusBar.addShortcutKeypress(kbF10, cmMenu,
205 i18n.getString("statusBarMenu"));
206
207 // Spin it up
208 terminal = new TTerminalWidget(this, 0, 0, new TAction() {
209 public void DO() {
210 onShellExit();
211 }
212 });
213 }
214
215 // ------------------------------------------------------------------------
216 // TScrollableWindow ------------------------------------------------------
217 // ------------------------------------------------------------------------
218
219 /**
220 * Draw the display buffer.
221 */
222 @Override
223 public void draw() {
224 if (terminal != null) {
225 setTitle(terminal.getTitle());
226 }
227 reflowData();
228 super.draw();
229 }
230
231 /**
232 * Handle window/screen resize events.
233 *
234 * @param resize resize event
235 */
236 @Override
237 public void onResize(final TResizeEvent resize) {
238 if (resize.getType() == TResizeEvent.Type.WIDGET) {
239 if (terminal != null) {
240 terminal.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET,
241 getWidth() - 2, getHeight() - 2));
242 }
243
244 // Resize the scroll bars
245 reflowData();
246 placeScrollbars();
247 }
248 return;
249 }
250
251 /**
252 * Resize scrollbars for a new width/height.
253 */
254 @Override
255 public void reflowData() {
256 // Vertical scrollbar
257 if (terminal != null) {
258 terminal.reflowData();
259 setTopValue(terminal.getTopValue());
260 setBottomValue(terminal.getBottomValue());
261 setVerticalBigChange(terminal.getVerticalBigChange());
262 setVerticalValue(terminal.getVerticalValue());
263 }
264 }
265
266 /**
267 * Handle keystrokes.
268 *
269 * @param keypress keystroke event
270 */
271 @Override
272 public void onKeypress(final TKeypressEvent keypress) {
273 if ((terminal != null)
274 && (terminal.isReading())
275 && (!inKeyboardResize)
276 ) {
277 terminal.onKeypress(keypress);
278 } else {
279 super.onKeypress(keypress);
280 }
281 }
282
283 /**
284 * Handle mouse press events.
285 *
286 * @param mouse mouse button press event
287 */
288 @Override
289 public void onMouseDown(final TMouseEvent mouse) {
290 if (inWindowMove || inWindowResize) {
291 // TWindow needs to deal with this.
292 super.onMouseDown(mouse);
293 return;
294 }
295
296 super.onMouseDown(mouse);
297 }
298
299 /**
300 * Handle mouse release events.
301 *
302 * @param mouse mouse button release event
303 */
304 @Override
305 public void onMouseUp(final TMouseEvent mouse) {
306 if (inWindowMove || inWindowResize) {
307 // TWindow needs to deal with this.
308 super.onMouseUp(mouse);
309 return;
310 }
311
312 super.onMouseUp(mouse);
313
314 if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
315 // Clicked on vertical scrollbar
316 if (terminal != null) {
317 terminal.setVerticalValue(getVerticalValue());
318 }
319 }
320 }
321
322 /**
323 * Handle mouse motion events.
324 *
325 * @param mouse mouse motion event
326 */
327 @Override
328 public void onMouseMotion(final TMouseEvent mouse) {
329 if (inWindowMove || inWindowResize) {
330 // TWindow needs to deal with this.
331 super.onMouseMotion(mouse);
332 return;
333 }
334
335 super.onMouseMotion(mouse);
336
337 if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) {
338 // Clicked/dragged on vertical scrollbar
339 if (terminal != null) {
340 terminal.setVerticalValue(getVerticalValue());
341 }
342 }
343 }
344
345 /**
346 * Get this window's help topic to load.
347 *
348 * @return the topic name
349 */
350 @Override
351 public String getHelpTopic() {
352 return "Terminal Window";
353 }
354
355 // ------------------------------------------------------------------------
356 // TTerminalWindow --------------------------------------------------------
357 // ------------------------------------------------------------------------
358
359 /**
360 * Returns true if this window does not want the application-wide mouse
361 * cursor drawn over it.
362 *
363 * @return true if this window does not want the application-wide mouse
364 * cursor drawn over it
365 */
366 @Override
367 public boolean hasHiddenMouse() {
368 if (terminal != null) {
369 return terminal.hasHiddenMouse();
370 }
371 return false;
372 }
373
374 /**
375 * Claim the keystrokes the emulator will need.
376 */
377 private void addShortcutKeys() {
378 addShortcutKeypress(kbCtrlA);
379 addShortcutKeypress(kbCtrlB);
380 addShortcutKeypress(kbCtrlC);
381 addShortcutKeypress(kbCtrlD);
382 addShortcutKeypress(kbCtrlE);
383 addShortcutKeypress(kbCtrlF);
384 addShortcutKeypress(kbCtrlG);
385 addShortcutKeypress(kbCtrlH);
386 addShortcutKeypress(kbCtrlU);
387 addShortcutKeypress(kbCtrlJ);
388 addShortcutKeypress(kbCtrlK);
389 addShortcutKeypress(kbCtrlL);
390 addShortcutKeypress(kbCtrlM);
391 addShortcutKeypress(kbCtrlN);
392 addShortcutKeypress(kbCtrlO);
393 addShortcutKeypress(kbCtrlP);
394 addShortcutKeypress(kbCtrlQ);
395 addShortcutKeypress(kbCtrlR);
396 addShortcutKeypress(kbCtrlS);
397 addShortcutKeypress(kbCtrlT);
398 addShortcutKeypress(kbCtrlU);
399 addShortcutKeypress(kbCtrlV);
400 addShortcutKeypress(kbCtrlW);
401 addShortcutKeypress(kbCtrlX);
402 addShortcutKeypress(kbCtrlY);
403 addShortcutKeypress(kbCtrlZ);
404 addShortcutKeypress(kbF1);
405 addShortcutKeypress(kbF2);
406 addShortcutKeypress(kbF3);
407 addShortcutKeypress(kbF4);
408 addShortcutKeypress(kbF5);
409 addShortcutKeypress(kbF6);
410 addShortcutKeypress(kbF7);
411 addShortcutKeypress(kbF8);
412 addShortcutKeypress(kbF9);
413 addShortcutKeypress(kbF10);
414 addShortcutKeypress(kbF11);
415 addShortcutKeypress(kbF12);
416 addShortcutKeypress(kbAltA);
417 addShortcutKeypress(kbAltB);
418 addShortcutKeypress(kbAltC);
419 addShortcutKeypress(kbAltD);
420 addShortcutKeypress(kbAltE);
421 addShortcutKeypress(kbAltF);
422 addShortcutKeypress(kbAltG);
423 addShortcutKeypress(kbAltH);
424 addShortcutKeypress(kbAltU);
425 addShortcutKeypress(kbAltJ);
426 addShortcutKeypress(kbAltK);
427 addShortcutKeypress(kbAltL);
428 addShortcutKeypress(kbAltM);
429 addShortcutKeypress(kbAltN);
430 addShortcutKeypress(kbAltO);
431 addShortcutKeypress(kbAltP);
432 addShortcutKeypress(kbAltQ);
433 addShortcutKeypress(kbAltR);
434 addShortcutKeypress(kbAltS);
435 addShortcutKeypress(kbAltT);
436 addShortcutKeypress(kbAltU);
437 addShortcutKeypress(kbAltV);
438 addShortcutKeypress(kbAltW);
439 addShortcutKeypress(kbAltX);
440 addShortcutKeypress(kbAltY);
441 addShortcutKeypress(kbAltZ);
442 }
443
444 /**
445 * Hook for subclasses to be notified of the shell termination.
446 */
447 public void onShellExit() {
448 if (closeOnExit) {
449 close();
450 }
451 clearShortcutKeypresses();
452 getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT));
453 }
454
455 /**
456 * Wait for a period of time to get output from the launched process.
457 *
458 * @param millis millis to wait for, or 0 to wait forever
459 * @return true if the launched process has emitted something
460 */
461 public boolean waitForOutput(final int millis) {
462 if (terminal == null) {
463 return false;
464 }
465 return terminal.waitForOutput(millis);
466 }
467
468 /**
469 * Get the exit value for the emulator.
470 *
471 * @return exit value
472 */
473 public int getExitValue() {
474 if (terminal == null) {
475 return -1;
476 }
477 return terminal.getExitValue();
478 }
479
480 }