package com.bytezone.dm3270.application; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Optional; import com.bytezone.dm3270.application.Parameters.SiteParameters; import com.bytezone.dm3270.attributes.StartFieldAttribute; import com.bytezone.dm3270.commands.AIDCommand; import com.bytezone.dm3270.display.CursorMoveListener; import com.bytezone.dm3270.display.Field; import com.bytezone.dm3270.display.FieldChangeListener; import com.bytezone.dm3270.display.FontManager; import com.bytezone.dm3270.display.HistoryManager; import com.bytezone.dm3270.display.HistoryScreen; import com.bytezone.dm3270.display.Screen; import com.bytezone.dm3270.display.ScreenDimensions; import com.bytezone.dm3270.extended.CommandHeader; import com.bytezone.dm3270.extended.TN3270ExtendedCommand; import com.bytezone.dm3270.plugins.PluginsStage; import com.bytezone.dm3270.streams.TelnetListener; import com.bytezone.dm3270.streams.TelnetState; import com.bytezone.dm3270.streams.TerminalServer; import com.bytezone.dm3270.utilities.Dm3270Utility; import com.bytezone.dm3270.utilities.ISite; import com.bytezone.dm3270.utilities.Site; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.Pos; import javafx.scene.control.Label; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.control.Separator; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.text.Font; public class ConsolePane extends BorderPane implements FieldChangeListener, CursorMoveListener, KeyboardStatusListener { private final static int MARGIN = 4; private final static int GAP = 12; private final static String OS = System.getProperty ("os.name"); private final static boolean SYSTEM_MENUBAR = OS != null && OS.startsWith ("Mac"); private final Parameters parameters = new Parameters (); private final Screen screen; private final Label status = new Label (); private final Label insertMode = new Label (); private final Label cursorLocation = new Label (); private final Label fieldType = new Label (); private final Label fieldLocation = new Label (); private TelnetListener telnetListener; private final TelnetState telnetState; private int commandHeaderCount; private ISite server; private boolean connected = false; private TerminalServer terminalServer; private Thread terminalServerThread; private HistoryManager screenHistory; // null unless showing screen history private HBox historyBox; // status display area private final Label historyLabel = new Label (); // status text private final BorderPane statusPane; private final MenuBar menuBar = new MenuBar (); private MenuItem menuItemAssistant; private MenuItem menuItemConsoleLog; private final FontManager fontManager; public ConsolePane (Screen screen, ISite server, PluginsStage pluginsStage) { this.screen = screen; this.telnetState = screen.getTelnetState (); this.server = server; this.fontManager = screen.getFontManager (); pluginsStage.setConsolePane (this); screen.setConsolePane (this); screen.getScreenCursor ().addFieldChangeListener (this); screen.getScreenCursor ().addCursorMoveListener (this); setMargin (screen, new Insets (MARGIN, MARGIN, 0, MARGIN)); menuBar.getMenus ().addAll (getCommandsMenu (), fontManager.getFontMenu ()); // allow null for replay testing if (server == null || server.getPlugins ()) menuBar.getMenus ().add (pluginsStage.getMenu (server)); setTop (menuBar); setCenter (screen); setBottom (statusPane = getStatusBar ()); menuBar.setUseSystemMenuBar (SYSTEM_MENUBAR); setHistoryBar (); if (server != null) { Optional<SiteParameters> sp = parameters.getSiteParameters (server.getName ()); if (sp.isPresent ()) { String offset = sp.get ().getParameter ("offset"); if (!offset.isEmpty () && offset.length () > 4) { char direction = offset.charAt (3); int value = Integer.parseInt (offset.substring (4)); ZonedDateTime now = ZonedDateTime.now (ZoneOffset.UTC); if (direction == '+') now = now.plusHours (value); else now = now.minusHours (value); } } } screen.requestFocus (); } public void setSite( ISite site ) { server = site; } public void setStatusText (String text) { status.setText (text); } private Menu getCommandsMenu () { Menu menuCommands = new Menu ("Commands"); MenuItem menuItemToggleScreens = getMenuItem ("Screen history", e -> toggleHistory (), KeyCode.S); menuItemAssistant = getMenuItem ("Transfers", e -> screen.getAssistantStage ().show (), KeyCode.T); menuItemConsoleLog = getMenuItem ("Console log", e -> screen.getConsoleLogStage ().show (), KeyCode.L); setIsConsole (false); menuCommands.getItems ().addAll (menuItemToggleScreens, menuItemAssistant, menuItemConsoleLog, new SeparatorMenuItem (), screen.getMenuItemUpload (), screen.getMenuItemDownload ()); if (!SYSTEM_MENUBAR) { MenuItem quitMenuItem = new MenuItem ("Quit"); menuCommands.getItems ().addAll (new SeparatorMenuItem (), quitMenuItem); quitMenuItem.setOnAction (e -> Platform.exit ()); quitMenuItem.setAccelerator (new KeyCodeCombination (KeyCode.Q, KeyCombination.SHORTCUT_DOWN)); } return menuCommands; } private MenuItem getMenuItem (String text, EventHandler<ActionEvent> eventHandler, KeyCode keyCode) { MenuItem menuItem = new MenuItem (text); menuItem.setOnAction (eventHandler); menuItem .setAccelerator (new KeyCodeCombination (keyCode, KeyCombination.SHORTCUT_DOWN)); return menuItem; } private BorderPane getStatusBar () { Separator[] div = new Separator[6]; for (int i = 0; i < div.length; i++) { div[i] = new Separator (); div[i].setOrientation (Orientation.VERTICAL); } HBox leftBox = getHBox (new Insets (2, GAP, 2, 3), Pos.CENTER_LEFT); leftBox.getChildren ().addAll (div[0], fieldLocation, div[1], insertMode, div[2]); HBox centerBox = getHBox (new Insets (2, GAP, 2, GAP), Pos.CENTER); centerBox.getChildren ().add (status); HBox rightBox = getHBox (new Insets (2, 0, 2, GAP), Pos.CENTER_RIGHT); rightBox.getChildren ().addAll (div[3], fieldType, div[4], cursorLocation, div[5]); setStatusFont (); BorderPane statusPane = new BorderPane (); statusPane.setLeft (leftBox); statusPane.setCenter (centerBox); statusPane.setRight (rightBox); return statusPane; } public void setIsConsole (boolean value) { menuItemConsoleLog.setDisable (!value); menuItemAssistant.setDisable (value); } // called from this.getStatusBar() // called from Screen.fontChanged() public void setStatusFont () { Font font = fontManager.getStatusBarFont (); status.setFont (font); insertMode.setFont (font); cursorLocation.setFont (font); fieldType.setFont (font); fieldLocation.setFont (font); } private void setHistoryBar () { historyBox = getHBox (new Insets (2, GAP, 2, GAP), Pos.CENTER); historyBox.getChildren ().add (historyLabel); Font statusBarFont = fontManager.getStatusBarFont (); historyLabel.setFont (statusBarFont); } private HBox getHBox (Insets insets, Pos alignment) { HBox hbox = new HBox (); hbox.setPadding (insets); hbox.setSpacing (10); hbox.setAlignment (alignment); return hbox; } private void toggleHistory () { if (screenHistory == null) // in normal screen mode { Optional<HistoryManager> opt = screen.pause (); if (opt.isPresent ()) { screenHistory = opt.get (); changeScreen (screenHistory.current ()); setBottom (historyBox); } } else // in screen history mode { screenHistory = null; setCenter (screen); setBottom (statusPane); screen.resume (); setStyle (null); } } void back () { if (screenHistory != null && screenHistory.hasPrevious ()) changeScreen (screenHistory.previous ()); } void forward () { if (screenHistory != null && screenHistory.hasNext ()) changeScreen (screenHistory.next ()); } private void changeScreen (HistoryScreen historyScreen) { historyScreen.drawScreen (screen.getFontManager ().getFontDetails ()); setCenter (historyScreen); setMargin (historyScreen, new Insets (MARGIN, MARGIN, 0, MARGIN)); setStyle ("-fx-background-color: navajowhite;"); historyLabel.setText (String.format ("Screen %02d of %02d", screenHistory.getCurrentIndex () + 1, screenHistory.size ())); } // called from ConsoleKeyPress.handle (KeyEvent e) // called from TSOCommand.execute() public void sendAID (byte aid, String name) { if (screen.isInsertMode ()) screen.toggleInsertMode (); screen.lockKeyboard (name); screen.setAID (aid); AIDCommand command = screen.readModifiedFields (); sendAID (command); } // called from PluginsStage.processPluginRequest (Plugin plugin) public void sendAID (AIDCommand command) { assert telnetState != null; if (telnetState.does3270Extended ()) { byte[] buffer = new byte[5]; Dm3270Utility.packUnsignedShort (commandHeaderCount++, buffer, 3); CommandHeader header = new CommandHeader (buffer); TN3270ExtendedCommand extendedCommand = new TN3270ExtendedCommand (header, command); telnetState.write (extendedCommand.getTelnetData ()); } else telnetState.write (command.getTelnetData ()); } // called from Console.startSelectedFunction() // called from Terminal.start() public void connect () { if (server == null) throw new IllegalArgumentException ("Server must not be null"); // set preferences for this session telnetState.setDo3270Extended (server.getExtended ()); telnetState.setDoTerminalType (true); telnetListener = new TelnetListener (screen, telnetState); terminalServer = new TerminalServer (server.getURL (), server.getPort (), telnetListener); telnetState.setTerminalServer (terminalServer); terminalServerThread = new Thread (terminalServer); terminalServerThread.start (); connected = true; } public boolean isConnected() { return connected; } public void disconnect () { if (terminalServer != null) terminalServer.close (); telnetState.close (); if (terminalServerThread != null) { terminalServerThread.interrupt (); try { terminalServerThread.join (); } catch (InterruptedException e) { e.printStackTrace (); } connected = false; } } @Override public void fieldChanged (Field oldField, Field newField) { if (newField == null) { fieldType.setText (" "); fieldLocation.setText ("0000/0000"); } else { StartFieldAttribute sfa = newField.getStartFieldAttribute (); fieldType.setText (String.format ("%6.6s", sfa.getAcronym ())); fieldLocation.setText (String.format ("%04d/%04d", newField.getCursorOffset (), newField.getDisplayLength ())); } } @Override public void cursorMoved (int oldLocation, int newLocation, Field currentField) { ScreenDimensions screenDimensions = screen.getScreenDimensions (); int row = newLocation / screenDimensions.columns; int col = newLocation % screenDimensions.columns; cursorLocation.setText (String.format ("%03d/%03d", row, col)); fieldChanged (currentField, currentField); // update the acronym } @Override public void keyboardStatusChanged (KeyboardStatusChangedEvent evt) { setStatusText (evt.keyboardLocked ? evt.keyName : " "); insertMode.setText (evt.insertMode ? "Insert" : " "); } public void setHidden( boolean val ) { setVisible( val ); menuBar.setVisible( val ); screen.setVisible( val ); statusPane.setVisible( val ); } }