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