/* * Jexer - Java Text User Interface * * The MIT License (MIT) * * Copyright (C) 2019 Kevin Lamonte * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * @author Kevin Lamonte [kevin.lamonte@gmail.com] * @version 1 */ package jexer; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.io.InputStream; import java.io.IOException; import java.lang.reflect.Field; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import jexer.backend.ECMA48Terminal; import jexer.backend.GlyphMaker; import jexer.backend.MultiScreen; import jexer.backend.SwingTerminal; import jexer.bits.Cell; import jexer.bits.CellAttributes; import jexer.event.TKeypressEvent; import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; import jexer.menu.TMenu; import jexer.tterminal.DisplayLine; import jexer.tterminal.DisplayListener; import jexer.tterminal.ECMA48; import static jexer.TKeypress.*; /** * TTerminalWindow exposes a ECMA-48 / ANSI X3.64 style terminal in a window. */ public class TTerminalWindow extends TScrollableWindow { /** * Translated strings. */ private static final ResourceBundle i18n = ResourceBundle.getBundle(TTerminalWindow.class.getName()); // ------------------------------------------------------------------------ // Variables -------------------------------------------------------------- // ------------------------------------------------------------------------ /** * The terminal. */ private TTerminalWidget terminal; /** * If true, close the window when the shell exits. */ private boolean closeOnExit = false; // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ /** * Public constructor spawns a custom command line. * * @param application TApplication that manages this window * @param x column relative to parent * @param y row relative to parent * @param commandLine the command line to execute */ public TTerminalWindow(final TApplication application, final int x, final int y, final String commandLine) { this(application, x, y, RESIZABLE, commandLine.split("\\s+"), System.getProperty("jexer.TTerminal.closeOnExit", "false").equals("true")); } /** * Public constructor spawns a custom command line. * * @param application TApplication that manages this window * @param x column relative to parent * @param y row relative to parent * @param commandLine the command line to execute * @param closeOnExit if true, close the window when the command exits */ public TTerminalWindow(final TApplication application, final int x, final int y, final String commandLine, final boolean closeOnExit) { this(application, x, y, RESIZABLE, commandLine.split("\\s+"), closeOnExit); } /** * Public constructor spawns a custom command line. * * @param application TApplication that manages this window * @param x column relative to parent * @param y row relative to parent * @param flags mask of CENTERED, MODAL, or RESIZABLE * @param command the command line to execute */ public TTerminalWindow(final TApplication application, final int x, final int y, final int flags, final String [] command) { this(application, x, y, flags, command, System.getProperty("jexer.TTerminal.closeOnExit", "false").equals("true")); } /** * Public constructor spawns a custom command line. * * @param application TApplication that manages this window * @param x column relative to parent * @param y row relative to parent * @param flags mask of CENTERED, MODAL, or RESIZABLE * @param command the command line to execute * @param closeOnExit if true, close the window when the command exits */ public TTerminalWindow(final TApplication application, final int x, final int y, final int flags, final String [] command, final boolean closeOnExit) { super(application, i18n.getString("windowTitle"), x, y, 80 + 2, 24 + 2, flags); // Require at least one line for the display. setMinimumWindowHeight(3); this.closeOnExit = closeOnExit; vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2); // Claim the keystrokes the emulator will need. addShortcutKeys(); // Add shortcut text newStatusBar(i18n.getString("statusBarRunning")); // Spin it up terminal = new TTerminalWidget(this, 0, 0, new TAction() { public void DO() { onShellExit(); } }); } /** * Public constructor spawns a shell. * * @param application TApplication that manages this window * @param x column relative to parent * @param y row relative to parent * @param flags mask of CENTERED, MODAL, or RESIZABLE */ public TTerminalWindow(final TApplication application, final int x, final int y, final int flags) { this(application, x, y, flags, System.getProperty("jexer.TTerminal.closeOnExit", "false").equals("true")); } /** * Public constructor spawns a shell. * * @param application TApplication that manages this window * @param x column relative to parent * @param y row relative to parent * @param flags mask of CENTERED, MODAL, or RESIZABLE * @param closeOnExit if true, close the window when the shell exits */ public TTerminalWindow(final TApplication application, final int x, final int y, final int flags, final boolean closeOnExit) { super(application, i18n.getString("windowTitle"), x, y, 80 + 2, 24 + 2, flags); // Require at least one line for the display. setMinimumWindowHeight(3); this.closeOnExit = closeOnExit; vScroller = new TVScroller(this, getWidth() - 2, 0, getHeight() - 2); // Claim the keystrokes the emulator will need. addShortcutKeys(); // Add shortcut text newStatusBar(i18n.getString("statusBarRunning")); // Spin it up terminal = new TTerminalWidget(this, 0, 0, new TAction() { public void DO() { onShellExit(); } }); } // ------------------------------------------------------------------------ // TScrollableWindow ------------------------------------------------------ // ------------------------------------------------------------------------ /** * Draw the display buffer. */ @Override public void draw() { if (terminal != null) { setTitle(terminal.getTitle()); } reflowData(); super.draw(); } /** * Handle window/screen resize events. * * @param resize resize event */ @Override public void onResize(final TResizeEvent resize) { if (resize.getType() == TResizeEvent.Type.WIDGET) { if (terminal != null) { terminal.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, getWidth() - 2, getHeight() - 2)); } // Resize the scroll bars reflowData(); placeScrollbars(); } return; } /** * Resize scrollbars for a new width/height. */ @Override public void reflowData() { // Vertical scrollbar if (terminal != null) { terminal.reflowData(); setTopValue(terminal.getTopValue()); setBottomValue(terminal.getBottomValue()); setVerticalBigChange(terminal.getVerticalBigChange()); setVerticalValue(terminal.getVerticalValue()); } } /** * Handle keystrokes. * * @param keypress keystroke event */ @Override public void onKeypress(final TKeypressEvent keypress) { if ((terminal != null) && (terminal.isReading())) { terminal.onKeypress(keypress); } else { super.onKeypress(keypress); } } /** * Handle mouse press events. * * @param mouse mouse button press event */ @Override public void onMouseDown(final TMouseEvent mouse) { if (inWindowMove || inWindowResize) { // TWindow needs to deal with this. super.onMouseDown(mouse); return; } super.onMouseDown(mouse); } /** * Handle mouse release events. * * @param mouse mouse button release event */ @Override public void onMouseUp(final TMouseEvent mouse) { if (inWindowMove || inWindowResize) { // TWindow needs to deal with this. super.onMouseUp(mouse); return; } super.onMouseUp(mouse); if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) { // Clicked on vertical scrollbar if (terminal != null) { terminal.setVerticalValue(getVerticalValue()); } } } /** * Handle mouse motion events. * * @param mouse mouse motion event */ @Override public void onMouseMotion(final TMouseEvent mouse) { if (inWindowMove || inWindowResize) { // TWindow needs to deal with this. super.onMouseMotion(mouse); return; } super.onMouseMotion(mouse); if (mouse.isMouse1() && mouseOnVerticalScroller(mouse)) { // Clicked/dragged on vertical scrollbar if (terminal != null) { terminal.setVerticalValue(getVerticalValue()); } } } // ------------------------------------------------------------------------ // TTerminalWindow -------------------------------------------------------- // ------------------------------------------------------------------------ /** * Returns true if this window does not want the application-wide mouse * cursor drawn over it. * * @return true if this window does not want the application-wide mouse * cursor drawn over it */ @Override public boolean hasHiddenMouse() { if (terminal != null) { return terminal.hasHiddenMouse(); } return false; } /** * Claim the keystrokes the emulator will need. */ private void addShortcutKeys() { addShortcutKeypress(kbCtrlA); addShortcutKeypress(kbCtrlB); addShortcutKeypress(kbCtrlC); addShortcutKeypress(kbCtrlD); addShortcutKeypress(kbCtrlE); addShortcutKeypress(kbCtrlF); addShortcutKeypress(kbCtrlG); addShortcutKeypress(kbCtrlH); addShortcutKeypress(kbCtrlU); addShortcutKeypress(kbCtrlJ); addShortcutKeypress(kbCtrlK); addShortcutKeypress(kbCtrlL); addShortcutKeypress(kbCtrlM); addShortcutKeypress(kbCtrlN); addShortcutKeypress(kbCtrlO); addShortcutKeypress(kbCtrlP); addShortcutKeypress(kbCtrlQ); addShortcutKeypress(kbCtrlR); addShortcutKeypress(kbCtrlS); addShortcutKeypress(kbCtrlT); addShortcutKeypress(kbCtrlU); addShortcutKeypress(kbCtrlV); addShortcutKeypress(kbCtrlW); addShortcutKeypress(kbCtrlX); addShortcutKeypress(kbCtrlY); addShortcutKeypress(kbCtrlZ); addShortcutKeypress(kbF1); addShortcutKeypress(kbF2); addShortcutKeypress(kbF3); addShortcutKeypress(kbF4); addShortcutKeypress(kbF5); addShortcutKeypress(kbF6); addShortcutKeypress(kbF7); addShortcutKeypress(kbF8); addShortcutKeypress(kbF9); addShortcutKeypress(kbF10); addShortcutKeypress(kbF11); addShortcutKeypress(kbF12); addShortcutKeypress(kbAltA); addShortcutKeypress(kbAltB); addShortcutKeypress(kbAltC); addShortcutKeypress(kbAltD); addShortcutKeypress(kbAltE); addShortcutKeypress(kbAltF); addShortcutKeypress(kbAltG); addShortcutKeypress(kbAltH); addShortcutKeypress(kbAltU); addShortcutKeypress(kbAltJ); addShortcutKeypress(kbAltK); addShortcutKeypress(kbAltL); addShortcutKeypress(kbAltM); addShortcutKeypress(kbAltN); addShortcutKeypress(kbAltO); addShortcutKeypress(kbAltP); addShortcutKeypress(kbAltQ); addShortcutKeypress(kbAltR); addShortcutKeypress(kbAltS); addShortcutKeypress(kbAltT); addShortcutKeypress(kbAltU); addShortcutKeypress(kbAltV); addShortcutKeypress(kbAltW); addShortcutKeypress(kbAltX); addShortcutKeypress(kbAltY); addShortcutKeypress(kbAltZ); } /** * Hook for subclasses to be notified of the shell termination. */ public void onShellExit() { if (closeOnExit) { close(); } clearShortcutKeypresses(); getApplication().postEvent(new TMenuEvent(TMenu.MID_REPAINT)); } }