Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
30b1a77b61
|
|||
|
9bd1676ec1
|
@@ -1070,8 +1070,47 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Main ==========
|
private static void applyProxyUrl(ConnectionConfig config, String proxyUrl) {
|
||||||
|
if (config == null || proxyUrl == null || proxyUrl.trim().isEmpty()) return;
|
||||||
|
try {
|
||||||
|
java.net.URI uri = new java.net.URI(proxyUrl.trim());
|
||||||
|
String scheme = uri.getScheme() != null ? uri.getScheme().toLowerCase() : "http";
|
||||||
|
ConnectionConfig.ProxyType pType;
|
||||||
|
int pPort;
|
||||||
|
if (scheme.equals("http") || scheme.equals("https")) {
|
||||||
|
pType = ConnectionConfig.ProxyType.HTTP;
|
||||||
|
pPort = uri.getPort() > 0 ? uri.getPort() : 8080;
|
||||||
|
} else if (scheme.equals("socks4") || scheme.equals("socks4a")) {
|
||||||
|
pType = ConnectionConfig.ProxyType.SOCKS4;
|
||||||
|
pPort = uri.getPort() > 0 ? uri.getPort() : 1080;
|
||||||
|
} else if (scheme.equals("socks5") || scheme.equals("socks")) {
|
||||||
|
pType = ConnectionConfig.ProxyType.SOCKS5;
|
||||||
|
pPort = uri.getPort() > 0 ? uri.getPort() : 1080;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String pHost = uri.getHost();
|
||||||
|
String pUser = null;
|
||||||
|
String pPass = null;
|
||||||
|
String userInfo = uri.getUserInfo();
|
||||||
|
if (userInfo != null) {
|
||||||
|
int colon = userInfo.indexOf(':');
|
||||||
|
if (colon >= 0) {
|
||||||
|
pUser = userInfo.substring(0, colon);
|
||||||
|
pPass = userInfo.substring(colon + 1);
|
||||||
|
} else {
|
||||||
|
pUser = userInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pHost != null && !pHost.isEmpty()) {
|
||||||
|
config.setProxy(pType, pHost, pPort, pUser, pPass);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warning("Failed to parse CLI proxy URL: " + proxyUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Main ==========
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
boolean debug = false;
|
boolean debug = false;
|
||||||
@@ -1081,6 +1120,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
Boolean cliAutoSysUnlock = null;
|
Boolean cliAutoSysUnlock = null;
|
||||||
GraphicsMode cliGraphicsMode = null;
|
GraphicsMode cliGraphicsMode = null;
|
||||||
String configFile = null;
|
String configFile = null;
|
||||||
|
String cliProxyUrl = null;
|
||||||
|
String cliProfileName = null;
|
||||||
|
|
||||||
java.util.List<String> remainingArgs = new java.util.ArrayList<>();
|
java.util.List<String> remainingArgs = new java.util.ArrayList<>();
|
||||||
for (int i = 0; i < args.length; i++) {
|
for (int i = 0; i < args.length; i++) {
|
||||||
@@ -1107,6 +1148,14 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
cliGraphicsMode = GraphicsMode.NONE;
|
cliGraphicsMode = GraphicsMode.NONE;
|
||||||
} else if (("-c".equals(arg) || "--config".equals(arg)) && i + 1 < args.length) {
|
} else if (("-c".equals(arg) || "--config".equals(arg)) && i + 1 < args.length) {
|
||||||
configFile = args[++i];
|
configFile = args[++i];
|
||||||
|
} else if (arg.startsWith("--proxy=") || arg.startsWith("-proxy=")) {
|
||||||
|
cliProxyUrl = arg.substring(arg.indexOf('=') + 1).trim();
|
||||||
|
} else if (("--proxy".equals(arg) || "-proxy".equals(arg)) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||||
|
cliProxyUrl = args[++i].trim();
|
||||||
|
} else if (arg.startsWith("--profile=")) {
|
||||||
|
cliProfileName = arg.substring(arg.indexOf('=') + 1).trim();
|
||||||
|
} else if ("--profile".equals(arg) && i + 1 < args.length && !args[i + 1].startsWith("-")) {
|
||||||
|
cliProfileName = args[++i].trim();
|
||||||
} else if (arg.startsWith("-")) {
|
} else if (arg.startsWith("-")) {
|
||||||
System.err.println("Unknown option: " + arg);
|
System.err.println("Unknown option: " + arg);
|
||||||
} else {
|
} else {
|
||||||
@@ -1143,11 +1192,33 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
final Boolean finalTn3270e = cliTn3270e;
|
final Boolean finalTn3270e = cliTn3270e;
|
||||||
final Boolean finalAutoSysUnlock = cliAutoSysUnlock;
|
final Boolean finalAutoSysUnlock = cliAutoSysUnlock;
|
||||||
final GraphicsMode finalGraphicsMode = cliGraphicsMode;
|
final GraphicsMode finalGraphicsMode = cliGraphicsMode;
|
||||||
|
final String finalProxyUrl = cliProxyUrl;
|
||||||
|
final String finalProfileName = cliProfileName;
|
||||||
|
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
J3270App app = new J3270App();
|
J3270App app = new J3270App();
|
||||||
app.setVisible(true);
|
app.setVisible(true);
|
||||||
|
|
||||||
|
if (finalProfileName != null) {
|
||||||
|
haus.nightmare.j3270.storage.SavedHost profile = haus.nightmare.j3270.storage.HostStorage.findByName(finalProfileName);
|
||||||
|
if (profile == null) {
|
||||||
|
profile = haus.nightmare.j3270.storage.HostStorage.findById(finalProfileName);
|
||||||
|
}
|
||||||
|
if (profile != null) {
|
||||||
|
ConnectionConfig config = profile.toConnectionConfig();
|
||||||
|
if (finalTls) config.setUseTls(true);
|
||||||
|
if (finalNoVerify) config.setTlsVerifyCert(false);
|
||||||
|
if (finalTn3270e != null) config.setTn3270eEnabled(finalTn3270e);
|
||||||
|
if (finalAutoSysUnlock != null) config.setAutoSysUnlock(finalAutoSysUnlock);
|
||||||
|
if (finalGraphicsMode != null) config.setGraphicsMode(finalGraphicsMode);
|
||||||
|
if (finalProxyUrl != null) applyProxyUrl(config, finalProxyUrl);
|
||||||
|
app.connect(config);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
System.err.println("Profile not found: " + finalProfileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!remainingArgs.isEmpty()) {
|
if (!remainingArgs.isEmpty()) {
|
||||||
String hostArg = remainingArgs.get(0);
|
String hostArg = remainingArgs.get(0);
|
||||||
int port = finalTls ? 992 : 23;
|
int port = finalTls ? 992 : 23;
|
||||||
@@ -1188,6 +1259,9 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
} else {
|
} else {
|
||||||
config.setGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
config.setGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||||
}
|
}
|
||||||
|
if (finalProxyUrl != null) {
|
||||||
|
applyProxyUrl(config, finalProxyUrl);
|
||||||
|
}
|
||||||
app.connect(config);
|
app.connect(config);
|
||||||
} else {
|
} else {
|
||||||
haus.nightmare.j3270.config.Settings.StartupBehavior behavior = haus.nightmare.j3270.config.Settings
|
haus.nightmare.j3270.config.Settings.StartupBehavior behavior = haus.nightmare.j3270.config.Settings
|
||||||
@@ -1197,6 +1271,17 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
SwingUtilities.invokeLater(app::showConnectDialog);
|
SwingUtilities.invokeLater(app::showConnectDialog);
|
||||||
break;
|
break;
|
||||||
case AUTO_CONNECT:
|
case AUTO_CONNECT:
|
||||||
|
haus.nightmare.j3270.storage.SavedHost autoHost = haus.nightmare.j3270.storage.HostStorage.getAutoConnectHost();
|
||||||
|
if (autoHost != null) {
|
||||||
|
ConnectionConfig config = autoHost.toConnectionConfig();
|
||||||
|
if (finalTls) config.setUseTls(true);
|
||||||
|
if (finalNoVerify) config.setTlsVerifyCert(false);
|
||||||
|
if (finalTn3270e != null) config.setTn3270eEnabled(finalTn3270e);
|
||||||
|
if (finalAutoSysUnlock != null) config.setAutoSysUnlock(finalAutoSysUnlock);
|
||||||
|
if (finalGraphicsMode != null) config.setGraphicsMode(finalGraphicsMode);
|
||||||
|
if (finalProxyUrl != null) applyProxyUrl(config, finalProxyUrl);
|
||||||
|
app.connect(config);
|
||||||
|
} else {
|
||||||
String host = haus.nightmare.j3270.config.Settings.getAutoConnectHost();
|
String host = haus.nightmare.j3270.config.Settings.getAutoConnectHost();
|
||||||
int port = haus.nightmare.j3270.config.Settings.getAutoConnectPort();
|
int port = haus.nightmare.j3270.config.Settings.getAutoConnectPort();
|
||||||
boolean tls = haus.nightmare.j3270.config.Settings.getAutoConnectTls();
|
boolean tls = haus.nightmare.j3270.config.Settings.getAutoConnectTls();
|
||||||
@@ -1214,10 +1299,14 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate
|
|||||||
} else {
|
} else {
|
||||||
config.setGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
config.setGraphicsMode(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||||
}
|
}
|
||||||
|
if (finalProxyUrl != null) {
|
||||||
|
applyProxyUrl(config, finalProxyUrl);
|
||||||
|
}
|
||||||
app.connect(config);
|
app.connect(config);
|
||||||
} else {
|
} else {
|
||||||
SwingUtilities.invokeLater(app::showConnectDialog);
|
SwingUtilities.invokeLater(app::showConnectDialog);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DO_NOTHING:
|
case DO_NOTHING:
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -0,0 +1,434 @@
|
|||||||
|
package haus.nightmare.j3270.storage;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage manager for saved host profiles, maintaining 1:1 format compatibility with a3270.
|
||||||
|
* Persists profiles in Java user preferences with JSON serialization.
|
||||||
|
*/
|
||||||
|
public class HostStorage {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(HostStorage.class.getName());
|
||||||
|
private static final Preferences prefs = Preferences.userNodeForPackage(HostStorage.class);
|
||||||
|
public static final String KEY_SAVED_HOSTS = "saved_hosts";
|
||||||
|
|
||||||
|
public static synchronized List<SavedHost> getSavedHosts() {
|
||||||
|
String jsonStr = prefs.get(KEY_SAVED_HOSTS, null);
|
||||||
|
if (jsonStr == null || jsonStr.trim().isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return parseHostsJson(jsonStr);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.log(Level.WARNING, "Failed to parse saved hosts JSON", e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void saveHost(SavedHost host) {
|
||||||
|
if (host == null) return;
|
||||||
|
List<SavedHost> current = new ArrayList<>(getSavedHosts());
|
||||||
|
int index = -1;
|
||||||
|
for (int i = 0; i < current.size(); i++) {
|
||||||
|
if (Objects.equals(current.get(i).getId(), host.getId())) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this host is set to autoConnect, clear autoConnect on all others
|
||||||
|
SavedHost hostToSave = host.copy();
|
||||||
|
if (hostToSave.isAutoConnect()) {
|
||||||
|
for (int i = 0; i < current.size(); i++) {
|
||||||
|
current.get(i).setAutoConnect(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 0) {
|
||||||
|
current.set(index, hostToSave);
|
||||||
|
} else {
|
||||||
|
current.add(hostToSave);
|
||||||
|
}
|
||||||
|
saveAll(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void deleteHost(String hostId) {
|
||||||
|
if (hostId == null) return;
|
||||||
|
List<SavedHost> current = new ArrayList<>(getSavedHosts());
|
||||||
|
current.removeIf(h -> Objects.equals(h.getId(), hostId));
|
||||||
|
saveAll(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized SavedHost getAutoConnectHost() {
|
||||||
|
for (SavedHost h : getSavedHosts()) {
|
||||||
|
if (h.isAutoConnect()) {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized SavedHost findById(String id) {
|
||||||
|
if (id == null) return null;
|
||||||
|
for (SavedHost h : getSavedHosts()) {
|
||||||
|
if (id.equals(h.getId())) {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized SavedHost findByName(String name) {
|
||||||
|
if (name == null) return null;
|
||||||
|
for (SavedHost h : getSavedHosts()) {
|
||||||
|
if (name.equalsIgnoreCase(h.getName())) {
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void saveAll(List<SavedHost> hosts) {
|
||||||
|
if (hosts == null) return;
|
||||||
|
String json = serializeHostsJson(hosts);
|
||||||
|
prefs.put(KEY_SAVED_HOSTS, json);
|
||||||
|
try {
|
||||||
|
prefs.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void clearAll() {
|
||||||
|
prefs.remove(KEY_SAVED_HOSTS);
|
||||||
|
try {
|
||||||
|
prefs.flush();
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= JSON Serialization =================
|
||||||
|
|
||||||
|
public static String serializeHostsJson(List<SavedHost> hosts) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("[\n");
|
||||||
|
for (int i = 0; i < hosts.size(); i++) {
|
||||||
|
SavedHost h = hosts.get(i);
|
||||||
|
sb.append(" ").append(serializeHostJson(h));
|
||||||
|
if (i < hosts.size() - 1) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String serializeHostJson(SavedHost h) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
sb.append("{");
|
||||||
|
appendField(sb, "id", h.getId(), true);
|
||||||
|
appendField(sb, "name", h.getName(), true);
|
||||||
|
appendField(sb, "host", h.getHost(), true);
|
||||||
|
appendField(sb, "port", h.getPort(), true);
|
||||||
|
appendField(sb, "model", h.getModel(), true);
|
||||||
|
appendField(sb, "dynamicRows", h.getDynamicRows(), true);
|
||||||
|
appendField(sb, "dynamicCols", h.getDynamicCols(), true);
|
||||||
|
appendField(sb, "luName", h.getLuName(), true);
|
||||||
|
appendField(sb, "autoConnect", h.isAutoConnect(), true);
|
||||||
|
appendField(sb, "hostType", h.getHostType(), true);
|
||||||
|
appendField(sb, "useTls", h.isUseTls(), true);
|
||||||
|
appendField(sb, "tlsVerifyCert", h.isTlsVerifyCert(), true);
|
||||||
|
appendField(sb, "tn3270e", h.isTn3270e(), true);
|
||||||
|
appendField(sb, "graphicsMode", h.getGraphicsMode(), true);
|
||||||
|
appendField(sb, "codePage", h.getCodePage(), true);
|
||||||
|
appendField(sb, "proxyType", h.getProxyType(), true);
|
||||||
|
appendField(sb, "proxyHost", h.getProxyHost(), true);
|
||||||
|
appendField(sb, "proxyPort", h.getProxyPort(), true);
|
||||||
|
appendField(sb, "proxyUsername", h.getProxyUsername(), true);
|
||||||
|
appendField(sb, "proxyPassword", h.getProxyPassword(), false);
|
||||||
|
sb.append("}");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendField(StringBuilder sb, String key, String val, boolean comma) {
|
||||||
|
sb.append("\"").append(key).append("\":\"").append(escapeJson(val != null ? val : "")).append("\"");
|
||||||
|
if (comma) sb.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendField(StringBuilder sb, String key, int val, boolean comma) {
|
||||||
|
sb.append("\"").append(key).append("\":").append(val);
|
||||||
|
if (comma) sb.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void appendField(StringBuilder sb, String key, boolean val, boolean comma) {
|
||||||
|
sb.append("\"").append(key).append("\":").append(val);
|
||||||
|
if (comma) sb.append(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escapeJson(String s) {
|
||||||
|
if (s == null) return "";
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
char c = s.charAt(i);
|
||||||
|
switch (c) {
|
||||||
|
case '"': sb.append("\\\""); break;
|
||||||
|
case '\\': sb.append("\\\\"); break;
|
||||||
|
case '\b': sb.append("\\b"); break;
|
||||||
|
case '\f': sb.append("\\f"); break;
|
||||||
|
case '\n': sb.append("\\n"); break;
|
||||||
|
case '\r': sb.append("\\r"); break;
|
||||||
|
case '\t': sb.append("\\t"); break;
|
||||||
|
default:
|
||||||
|
if (c < 32) {
|
||||||
|
sb.append(String.format("\\u%04x", (int) c));
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= JSON Deserialization =================
|
||||||
|
|
||||||
|
public static List<SavedHost> parseHostsJson(String json) {
|
||||||
|
List<SavedHost> list = new ArrayList<>();
|
||||||
|
if (json == null || json.trim().isEmpty()) return list;
|
||||||
|
|
||||||
|
SimpleJsonParser parser = new SimpleJsonParser(json);
|
||||||
|
Object root = parser.parseValue();
|
||||||
|
if (root instanceof List) {
|
||||||
|
for (Object item : (List<?>) root) {
|
||||||
|
if (item instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> map = (Map<String, Object>) item;
|
||||||
|
list.add(fromMap(map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (root instanceof Map) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> map = (Map<String, Object>) root;
|
||||||
|
list.add(fromMap(map));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SavedHost fromMap(Map<String, Object> map) {
|
||||||
|
SavedHost h = new SavedHost();
|
||||||
|
if (map.containsKey("id")) h.setId(String.valueOf(map.get("id")));
|
||||||
|
if (map.containsKey("name")) h.setName(String.valueOf(map.get("name")));
|
||||||
|
if (map.containsKey("host")) h.setHost(String.valueOf(map.get("host")));
|
||||||
|
if (map.containsKey("port")) h.setPort(parseInt(map.get("port"), 23));
|
||||||
|
if (map.containsKey("model")) h.setModel(parseInt(map.get("model"), 4));
|
||||||
|
if (map.containsKey("dynamicRows")) h.setDynamicRows(parseInt(map.get("dynamicRows"), 62));
|
||||||
|
if (map.containsKey("dynamicCols")) h.setDynamicCols(parseInt(map.get("dynamicCols"), 160));
|
||||||
|
if (map.containsKey("luName")) h.setLuName(String.valueOf(map.get("luName")));
|
||||||
|
if (map.containsKey("autoConnect")) h.setAutoConnect(parseBool(map.get("autoConnect"), false));
|
||||||
|
if (map.containsKey("hostType")) h.setHostType(String.valueOf(map.get("hostType")));
|
||||||
|
if (map.containsKey("useTls")) h.setUseTls(parseBool(map.get("useTls"), false));
|
||||||
|
if (map.containsKey("tlsVerifyCert")) h.setTlsVerifyCert(parseBool(map.get("tlsVerifyCert"), true));
|
||||||
|
if (map.containsKey("tn3270e")) h.setTn3270e(parseBool(map.get("tn3270e"), true));
|
||||||
|
if (map.containsKey("graphicsMode")) {
|
||||||
|
String gm = String.valueOf(map.get("graphicsMode"));
|
||||||
|
if ("PROGRAMMED_SYMBOLS".equalsIgnoreCase(gm)) gm = "BOTH";
|
||||||
|
h.setGraphicsMode(gm);
|
||||||
|
}
|
||||||
|
if (map.containsKey("codePage")) h.setCodePage(String.valueOf(map.get("codePage")));
|
||||||
|
if (map.containsKey("proxyType")) h.setProxyType(String.valueOf(map.get("proxyType")));
|
||||||
|
if (map.containsKey("proxyHost")) h.setProxyHost(String.valueOf(map.get("proxyHost")));
|
||||||
|
if (map.containsKey("proxyPort")) h.setProxyPort(parseInt(map.get("proxyPort"), 0));
|
||||||
|
if (map.containsKey("proxyUsername")) h.setProxyUsername(String.valueOf(map.get("proxyUsername")));
|
||||||
|
if (map.containsKey("proxyPassword")) h.setProxyPassword(String.valueOf(map.get("proxyPassword")));
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int parseInt(Object obj, int def) {
|
||||||
|
if (obj == null) return def;
|
||||||
|
if (obj instanceof Number) return ((Number) obj).intValue();
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(String.valueOf(obj).trim());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean parseBool(Object obj, boolean def) {
|
||||||
|
if (obj == null) return def;
|
||||||
|
if (obj instanceof Boolean) return (Boolean) obj;
|
||||||
|
return Boolean.parseBoolean(String.valueOf(obj).trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimal pure-Java JSON parser for nested Maps, Lists, Strings, Numbers, and Booleans.
|
||||||
|
*/
|
||||||
|
static class SimpleJsonParser {
|
||||||
|
private final String src;
|
||||||
|
private int pos = 0;
|
||||||
|
|
||||||
|
SimpleJsonParser(String src) {
|
||||||
|
this.src = src;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object parseValue() {
|
||||||
|
skipWhitespace();
|
||||||
|
if (pos >= src.length()) return null;
|
||||||
|
char c = src.charAt(pos);
|
||||||
|
if (c == '{') return parseObject();
|
||||||
|
if (c == '[') return parseArray();
|
||||||
|
if (c == '"') return parseString();
|
||||||
|
if (c == 't' || c == 'f') return parseBoolean();
|
||||||
|
if (c == 'n') return parseNull();
|
||||||
|
if (c == '-' || Character.isDigit(c)) return parseNumber();
|
||||||
|
throw new IllegalArgumentException("Unexpected char '" + c + "' at position " + pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> parseObject() {
|
||||||
|
match('{');
|
||||||
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
|
skipWhitespace();
|
||||||
|
if (peek() == '}') {
|
||||||
|
pos++;
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
skipWhitespace();
|
||||||
|
String key = parseString();
|
||||||
|
skipWhitespace();
|
||||||
|
match(':');
|
||||||
|
Object val = parseValue();
|
||||||
|
map.put(key, val);
|
||||||
|
skipWhitespace();
|
||||||
|
char c = peek();
|
||||||
|
if (c == ',') {
|
||||||
|
pos++;
|
||||||
|
} else if (c == '}') {
|
||||||
|
pos++;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Object> parseArray() {
|
||||||
|
match('[');
|
||||||
|
List<Object> list = new ArrayList<>();
|
||||||
|
skipWhitespace();
|
||||||
|
if (peek() == ']') {
|
||||||
|
pos++;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
Object val = parseValue();
|
||||||
|
list.add(val);
|
||||||
|
skipWhitespace();
|
||||||
|
char c = peek();
|
||||||
|
if (c == ',') {
|
||||||
|
pos++;
|
||||||
|
} else if (c == ']') {
|
||||||
|
pos++;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseString() {
|
||||||
|
match('"');
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
while (pos < src.length()) {
|
||||||
|
char c = src.charAt(pos++);
|
||||||
|
if (c == '"') {
|
||||||
|
return sb.toString();
|
||||||
|
} else if (c == '\\') {
|
||||||
|
if (pos >= src.length()) break;
|
||||||
|
char esc = src.charAt(pos++);
|
||||||
|
switch (esc) {
|
||||||
|
case '"': sb.append('"'); break;
|
||||||
|
case '\\': sb.append('\\'); break;
|
||||||
|
case '/': sb.append('/'); break;
|
||||||
|
case 'b': sb.append('\b'); break;
|
||||||
|
case 'f': sb.append('\f'); break;
|
||||||
|
case 'n': sb.append('\n'); break;
|
||||||
|
case 'r': sb.append('\r'); break;
|
||||||
|
case 't': sb.append('\t'); break;
|
||||||
|
case 'u':
|
||||||
|
if (pos + 4 <= src.length()) {
|
||||||
|
String hex = src.substring(pos, pos + 4);
|
||||||
|
pos += 4;
|
||||||
|
try {
|
||||||
|
sb.append((char) Integer.parseInt(hex, 16));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
sb.append("?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.append(esc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sb.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Boolean parseBoolean() {
|
||||||
|
if (src.startsWith("true", pos)) {
|
||||||
|
pos += 4;
|
||||||
|
return true;
|
||||||
|
} else if (src.startsWith("false", pos)) {
|
||||||
|
pos += 5;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Expected boolean at " + pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object parseNull() {
|
||||||
|
if (src.startsWith("null", pos)) {
|
||||||
|
pos += 4;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Expected null at " + pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Number parseNumber() {
|
||||||
|
int start = pos;
|
||||||
|
if (src.charAt(pos) == '-') pos++;
|
||||||
|
while (pos < src.length() && (Character.isDigit(src.charAt(pos)) || src.charAt(pos) == '.' || src.charAt(pos) == 'e' || src.charAt(pos) == 'E' || src.charAt(pos) == '+' || src.charAt(pos) == '-')) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
String numStr = src.substring(start, pos);
|
||||||
|
if (numStr.contains(".")) {
|
||||||
|
return Double.parseDouble(numStr);
|
||||||
|
}
|
||||||
|
return Long.parseLong(numStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipWhitespace() {
|
||||||
|
while (pos < src.length() && Character.isWhitespace(src.charAt(pos))) {
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private char peek() {
|
||||||
|
return pos < src.length() ? src.charAt(pos) : '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
private void match(char expected) {
|
||||||
|
skipWhitespace();
|
||||||
|
if (pos >= src.length() || src.charAt(pos) != expected) {
|
||||||
|
throw new IllegalArgumentException("Expected '" + expected + "' at " + pos);
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
package haus.nightmare.j3270.storage;
|
||||||
|
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import haus.nightmare.lib3270j.graphics.GraphicsMode;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data model for a saved 3270 connection profile, matching the schema of a3270 SavedHost.
|
||||||
|
* Includes per-connection host, model, protocol, and proxy configuration.
|
||||||
|
*/
|
||||||
|
public class SavedHost {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
private String host;
|
||||||
|
private int port;
|
||||||
|
private int model;
|
||||||
|
private int dynamicRows;
|
||||||
|
private int dynamicCols;
|
||||||
|
private String luName;
|
||||||
|
private boolean autoConnect;
|
||||||
|
private String hostType;
|
||||||
|
private boolean useTls;
|
||||||
|
private boolean tlsVerifyCert;
|
||||||
|
private boolean tn3270e;
|
||||||
|
private String graphicsMode;
|
||||||
|
private String codePage;
|
||||||
|
|
||||||
|
// Per-connection proxy configuration
|
||||||
|
private String proxyType;
|
||||||
|
private String proxyHost;
|
||||||
|
private int proxyPort;
|
||||||
|
private String proxyUsername;
|
||||||
|
private String proxyPassword;
|
||||||
|
|
||||||
|
public SavedHost() {
|
||||||
|
this.id = UUID.randomUUID().toString();
|
||||||
|
this.name = "Mainframe";
|
||||||
|
this.host = "127.0.0.1";
|
||||||
|
this.port = 23;
|
||||||
|
this.model = 4;
|
||||||
|
this.dynamicRows = 62;
|
||||||
|
this.dynamicCols = 160;
|
||||||
|
this.luName = "";
|
||||||
|
this.autoConnect = false;
|
||||||
|
this.hostType = "TSO";
|
||||||
|
this.useTls = false;
|
||||||
|
this.tlsVerifyCert = true;
|
||||||
|
this.tn3270e = true;
|
||||||
|
this.graphicsMode = "BOTH";
|
||||||
|
this.codePage = "037";
|
||||||
|
this.proxyType = "NONE";
|
||||||
|
this.proxyHost = "";
|
||||||
|
this.proxyPort = 0;
|
||||||
|
this.proxyUsername = "";
|
||||||
|
this.proxyPassword = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SavedHost(String id, String name, String host, int port, int model,
|
||||||
|
int dynamicRows, int dynamicCols, String luName, boolean autoConnect,
|
||||||
|
String hostType, boolean useTls, boolean tlsVerifyCert, boolean tn3270e,
|
||||||
|
String graphicsMode, String codePage, String proxyType, String proxyHost,
|
||||||
|
int proxyPort, String proxyUsername, String proxyPassword) {
|
||||||
|
this.id = (id != null && !id.trim().isEmpty()) ? id : UUID.randomUUID().toString();
|
||||||
|
this.name = name != null ? name : "";
|
||||||
|
this.host = host != null ? host : "127.0.0.1";
|
||||||
|
this.port = port > 0 ? port : 23;
|
||||||
|
this.model = model;
|
||||||
|
this.dynamicRows = dynamicRows > 0 ? dynamicRows : 62;
|
||||||
|
this.dynamicCols = dynamicCols > 0 ? dynamicCols : 160;
|
||||||
|
this.luName = luName != null ? luName : "";
|
||||||
|
this.autoConnect = autoConnect;
|
||||||
|
this.hostType = hostType != null ? hostType : "TSO";
|
||||||
|
this.useTls = useTls;
|
||||||
|
this.tlsVerifyCert = tlsVerifyCert;
|
||||||
|
this.tn3270e = tn3270e;
|
||||||
|
this.graphicsMode = graphicsMode != null ? graphicsMode : "BOTH";
|
||||||
|
this.codePage = codePage != null ? codePage : "037";
|
||||||
|
this.proxyType = (proxyType != null && !proxyType.trim().isEmpty()) ? proxyType.toUpperCase() : "NONE";
|
||||||
|
this.proxyHost = proxyHost != null ? proxyHost : "";
|
||||||
|
this.proxyPort = proxyPort;
|
||||||
|
this.proxyUsername = proxyUsername != null ? proxyUsername : "";
|
||||||
|
this.proxyPassword = proxyPassword != null ? proxyPassword : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public SavedHost copy() {
|
||||||
|
return new SavedHost(id, name, host, port, model, dynamicRows, dynamicCols, luName,
|
||||||
|
autoConnect, hostType, useTls, tlsVerifyCert, tn3270e, graphicsMode, codePage,
|
||||||
|
proxyType, proxyHost, proxyPort, proxyUsername, proxyPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts this saved host profile into an active ConnectionConfig.
|
||||||
|
*/
|
||||||
|
public ConnectionConfig toConnectionConfig() {
|
||||||
|
TerminalModel tm;
|
||||||
|
if (model == 0) {
|
||||||
|
tm = TerminalModel.IBM_DYNAMIC;
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
tm = TerminalModel.forModel(model, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
tm = TerminalModel.IBM_3279_4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionConfig config = new ConnectionConfig(host, port, tm, useTls);
|
||||||
|
if (model == 0 || tm.isDynamic()) {
|
||||||
|
config.setDynamicDimensions(dynamicRows, dynamicCols);
|
||||||
|
}
|
||||||
|
if (luName != null && !luName.trim().isEmpty()) {
|
||||||
|
config.setLuName(luName.trim());
|
||||||
|
}
|
||||||
|
config.setTlsVerifyCert(tlsVerifyCert);
|
||||||
|
config.setTn3270eEnabled(tn3270e);
|
||||||
|
if (graphicsMode != null && !graphicsMode.isEmpty()) {
|
||||||
|
config.setGraphicsMode(GraphicsMode.fromString(graphicsMode));
|
||||||
|
}
|
||||||
|
if (codePage != null && !codePage.isEmpty()) {
|
||||||
|
config.setCodePage(codePage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply per-connection proxy if configured
|
||||||
|
if (proxyType != null && !proxyType.equalsIgnoreCase("NONE") && proxyHost != null && !proxyHost.trim().isEmpty()) {
|
||||||
|
try {
|
||||||
|
ConnectionConfig.ProxyType pt = ConnectionConfig.ProxyType.valueOf(proxyType.toUpperCase());
|
||||||
|
int pPort = proxyPort > 0 ? proxyPort : (pt == ConnectionConfig.ProxyType.HTTP ? 8080 : 1080);
|
||||||
|
config.setProxy(pt, proxyHost.trim(), pPort,
|
||||||
|
(proxyUsername != null && !proxyUsername.trim().isEmpty()) ? proxyUsername.trim() : null,
|
||||||
|
(proxyPassword != null && !proxyPassword.isEmpty()) ? proxyPassword : null);
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getters and Setters
|
||||||
|
|
||||||
|
public String getId() { return id; }
|
||||||
|
public void setId(String id) { this.id = id; }
|
||||||
|
|
||||||
|
public String getName() { return name; }
|
||||||
|
public void setName(String name) { this.name = name; }
|
||||||
|
|
||||||
|
public String getHost() { return host; }
|
||||||
|
public void setHost(String host) { this.host = host; }
|
||||||
|
|
||||||
|
public int getPort() { return port; }
|
||||||
|
public void setPort(int port) { this.port = port; }
|
||||||
|
|
||||||
|
public int getModel() { return model; }
|
||||||
|
public void setModel(int model) { this.model = model; }
|
||||||
|
|
||||||
|
public int getDynamicRows() { return dynamicRows; }
|
||||||
|
public void setDynamicRows(int dynamicRows) { this.dynamicRows = dynamicRows; }
|
||||||
|
|
||||||
|
public int getDynamicCols() { return dynamicCols; }
|
||||||
|
public void setDynamicCols(int dynamicCols) { this.dynamicCols = dynamicCols; }
|
||||||
|
|
||||||
|
public String getLuName() { return luName; }
|
||||||
|
public void setLuName(String luName) { this.luName = luName; }
|
||||||
|
|
||||||
|
public boolean isAutoConnect() { return autoConnect; }
|
||||||
|
public void setAutoConnect(boolean autoConnect) { this.autoConnect = autoConnect; }
|
||||||
|
|
||||||
|
public String getHostType() { return hostType; }
|
||||||
|
public void setHostType(String hostType) { this.hostType = hostType; }
|
||||||
|
|
||||||
|
public boolean isUseTls() { return useTls; }
|
||||||
|
public void setUseTls(boolean useTls) { this.useTls = useTls; }
|
||||||
|
|
||||||
|
public boolean isTlsVerifyCert() { return tlsVerifyCert; }
|
||||||
|
public void setTlsVerifyCert(boolean tlsVerifyCert) { this.tlsVerifyCert = tlsVerifyCert; }
|
||||||
|
|
||||||
|
public boolean isTn3270e() { return tn3270e; }
|
||||||
|
public void setTn3270e(boolean tn3270e) { this.tn3270e = tn3270e; }
|
||||||
|
|
||||||
|
public String getGraphicsMode() { return graphicsMode; }
|
||||||
|
public void setGraphicsMode(String graphicsMode) { this.graphicsMode = graphicsMode; }
|
||||||
|
|
||||||
|
public String getCodePage() { return codePage; }
|
||||||
|
public void setCodePage(String codePage) { this.codePage = codePage; }
|
||||||
|
|
||||||
|
public String getProxyType() { return proxyType != null ? proxyType : "NONE"; }
|
||||||
|
public void setProxyType(String proxyType) { this.proxyType = (proxyType != null) ? proxyType.toUpperCase() : "NONE"; }
|
||||||
|
|
||||||
|
public String getProxyHost() { return proxyHost != null ? proxyHost : ""; }
|
||||||
|
public void setProxyHost(String proxyHost) { this.proxyHost = proxyHost; }
|
||||||
|
|
||||||
|
public int getProxyPort() { return proxyPort; }
|
||||||
|
public void setProxyPort(int proxyPort) { this.proxyPort = proxyPort; }
|
||||||
|
|
||||||
|
public String getProxyUsername() { return proxyUsername != null ? proxyUsername : ""; }
|
||||||
|
public void setProxyUsername(String proxyUsername) { this.proxyUsername = proxyUsername; }
|
||||||
|
|
||||||
|
public String getProxyPassword() { return proxyPassword != null ? proxyPassword : ""; }
|
||||||
|
public void setProxyPassword(String proxyPassword) { this.proxyPassword = proxyPassword; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
if (name != null && !name.trim().isEmpty()) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return host + (port > 0 ? ":" + port : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
SavedHost savedHost = (SavedHost) o;
|
||||||
|
return Objects.equals(id, savedHost.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,30 @@
|
|||||||
package haus.nightmare.j3270.ui;
|
package haus.nightmare.j3270.ui;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.storage.HostStorage;
|
||||||
|
import haus.nightmare.j3270.storage.SavedHost;
|
||||||
import haus.nightmare.lib3270j.ConnectionConfig;
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
import haus.nightmare.lib3270j.TerminalModel;
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import javax.swing.border.TitledBorder;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connection dialog for entering host, port, model, and LU name.
|
* Connection dialog for entering host, port, model, LU name, and per-connection proxy configuration.
|
||||||
|
* Supports loading and saving host profiles matching the a3270 schema.
|
||||||
*/
|
*/
|
||||||
public class ConnectDialog extends JDialog {
|
public class ConnectDialog extends JDialog {
|
||||||
|
|
||||||
|
private static final String NEW_PROFILE_LABEL = "<New / Custom Connection>";
|
||||||
|
|
||||||
|
// Profile selector
|
||||||
|
private JComboBox<Object> profileCombo;
|
||||||
|
private JButton saveProfileBtn;
|
||||||
|
private JButton deleteProfileBtn;
|
||||||
|
private String selectedProfileId = null;
|
||||||
|
|
||||||
|
// Connection parameters
|
||||||
private JTextField hostField;
|
private JTextField hostField;
|
||||||
private JTextField portField;
|
private JTextField portField;
|
||||||
private JComboBox<TerminalModel> modelCombo;
|
private JComboBox<TerminalModel> modelCombo;
|
||||||
@@ -26,6 +40,14 @@ public class ConnectDialog extends JDialog {
|
|||||||
private JCheckBox tn3270eCheckBox;
|
private JCheckBox tn3270eCheckBox;
|
||||||
private JCheckBox keepAliveCheckBox;
|
private JCheckBox keepAliveCheckBox;
|
||||||
private JCheckBox autoReconnectCheckBox;
|
private JCheckBox autoReconnectCheckBox;
|
||||||
|
|
||||||
|
// Per-connection proxy configuration
|
||||||
|
private JComboBox<ConnectionConfig.ProxyType> proxyTypeCombo;
|
||||||
|
private JTextField proxyHostField;
|
||||||
|
private JTextField proxyPortField;
|
||||||
|
private JTextField proxyUserField;
|
||||||
|
private JPasswordField proxyPassField;
|
||||||
|
|
||||||
private boolean confirmed;
|
private boolean confirmed;
|
||||||
private ConnectionConfig result;
|
private ConnectionConfig result;
|
||||||
|
|
||||||
@@ -35,64 +57,114 @@ public class ConnectDialog extends JDialog {
|
|||||||
buildUI();
|
buildUI();
|
||||||
pack();
|
pack();
|
||||||
setLocationRelativeTo(parent);
|
setLocationRelativeTo(parent);
|
||||||
setResizable(false);
|
setResizable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildUI() {
|
private void buildUI() {
|
||||||
JPanel mainPanel = new JPanel(new GridBagLayout());
|
JPanel contentPanel = new JPanel(new GridBagLayout());
|
||||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
|
contentPanel.setBorder(BorderFactory.createEmptyBorder(12, 14, 12, 14));
|
||||||
GridBagConstraints gbc = new GridBagConstraints();
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
gbc.insets = new Insets(5, 6, 5, 6);
|
gbc.insets = new Insets(4, 5, 4, 5);
|
||||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
Font labelFont = ThemeManager.getUiFont();
|
Font labelFont = ThemeManager.getUiFont();
|
||||||
|
|
||||||
// Host
|
int gridy = 0;
|
||||||
|
|
||||||
|
// 0. Profile Row
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 0;
|
gbc.gridy = gridy;
|
||||||
|
gbc.weightx = 0;
|
||||||
|
JLabel profileLabel = new JLabel("Profile:");
|
||||||
|
profileLabel.setFont(labelFont);
|
||||||
|
contentPanel.add(profileLabel, gbc);
|
||||||
|
|
||||||
|
gbc.gridx = 1;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
JPanel profileBar = new JPanel(new BorderLayout(6, 0));
|
||||||
|
profileBar.setOpaque(false);
|
||||||
|
|
||||||
|
profileCombo = new JComboBox<>();
|
||||||
|
profileCombo.setFont(ThemeManager.getUiFont());
|
||||||
|
ThemeManager.styleComboBox(profileCombo);
|
||||||
|
refreshProfileCombo();
|
||||||
|
profileCombo.addActionListener(e -> onProfileSelectionChanged());
|
||||||
|
profileBar.add(profileCombo, BorderLayout.CENTER);
|
||||||
|
|
||||||
|
JPanel profileBtnBar = new JPanel(new FlowLayout(FlowLayout.RIGHT, 4, 0));
|
||||||
|
profileBtnBar.setOpaque(false);
|
||||||
|
|
||||||
|
saveProfileBtn = new JButton("Save Profile...");
|
||||||
|
ThemeManager.styleButton(saveProfileBtn, ThemeManager.ButtonVariant.DEFAULT);
|
||||||
|
saveProfileBtn.setFont(labelFont);
|
||||||
|
saveProfileBtn.addActionListener(e -> onSaveProfile());
|
||||||
|
|
||||||
|
deleteProfileBtn = new JButton("Delete");
|
||||||
|
ThemeManager.styleButton(deleteProfileBtn, ThemeManager.ButtonVariant.CANCEL);
|
||||||
|
deleteProfileBtn.setFont(labelFont);
|
||||||
|
deleteProfileBtn.setEnabled(false);
|
||||||
|
deleteProfileBtn.addActionListener(e -> onDeleteProfile());
|
||||||
|
|
||||||
|
profileBtnBar.add(saveProfileBtn);
|
||||||
|
profileBtnBar.add(deleteProfileBtn);
|
||||||
|
profileBar.add(profileBtnBar, BorderLayout.EAST);
|
||||||
|
contentPanel.add(profileBar, gbc);
|
||||||
|
|
||||||
|
gridy++;
|
||||||
|
|
||||||
|
// 1. Host
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = gridy;
|
||||||
|
gbc.weightx = 0;
|
||||||
JLabel hostLabel = new JLabel("Host:");
|
JLabel hostLabel = new JLabel("Host:");
|
||||||
hostLabel.setFont(labelFont);
|
hostLabel.setFont(labelFont);
|
||||||
mainPanel.add(hostLabel, gbc);
|
contentPanel.add(hostLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
hostField = createField(20);
|
hostField = createField(20);
|
||||||
mainPanel.add(hostField, gbc);
|
contentPanel.add(hostField, gbc);
|
||||||
|
|
||||||
// Port
|
gridy++;
|
||||||
|
|
||||||
|
// 2. Port
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 1;
|
gbc.gridy = gridy;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel portLabel = new JLabel("Port:");
|
JLabel portLabel = new JLabel("Port:");
|
||||||
portLabel.setFont(labelFont);
|
portLabel.setFont(labelFont);
|
||||||
mainPanel.add(portLabel, gbc);
|
contentPanel.add(portLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
portField = createField(6);
|
portField = createField(6);
|
||||||
portField.setText("23");
|
portField.setText("23");
|
||||||
mainPanel.add(portField, gbc);
|
contentPanel.add(portField, gbc);
|
||||||
|
|
||||||
// Model
|
gridy++;
|
||||||
|
|
||||||
|
// 3. Model
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 2;
|
gbc.gridy = gridy;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel modelLabel = new JLabel("Model:");
|
JLabel modelLabel = new JLabel("Model:");
|
||||||
modelLabel.setFont(labelFont);
|
modelLabel.setFont(labelFont);
|
||||||
mainPanel.add(modelLabel, gbc);
|
contentPanel.add(modelLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
modelCombo = new JComboBox<>(TerminalModel.values());
|
modelCombo = new JComboBox<>(TerminalModel.values());
|
||||||
modelCombo.setSelectedItem(TerminalModel.IBM_3279_4);
|
modelCombo.setSelectedItem(TerminalModel.IBM_3279_4);
|
||||||
modelCombo.setFont(ThemeManager.getMonospacedUiFont());
|
modelCombo.setFont(ThemeManager.getMonospacedUiFont());
|
||||||
ThemeManager.styleComboBox(modelCombo);
|
ThemeManager.styleComboBox(modelCombo);
|
||||||
mainPanel.add(modelCombo, gbc);
|
contentPanel.add(modelCombo, gbc);
|
||||||
|
|
||||||
// Dynamic Dimensions (shown when IBM-DYNAMIC is selected)
|
gridy++;
|
||||||
|
|
||||||
|
// 4. Dynamic Dimensions
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 3;
|
gbc.gridy = gridy;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
dynamicDimLabel = new JLabel("Screen Size:");
|
dynamicDimLabel = new JLabel("Screen Size:");
|
||||||
dynamicDimLabel.setFont(labelFont);
|
dynamicDimLabel.setFont(labelFont);
|
||||||
mainPanel.add(dynamicDimLabel, gbc);
|
contentPanel.add(dynamicDimLabel, gbc);
|
||||||
|
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
@@ -113,7 +185,7 @@ public class ConnectDialog extends JDialog {
|
|||||||
dynamicDimPanel.add(dynamicRowsSpinner);
|
dynamicDimPanel.add(dynamicRowsSpinner);
|
||||||
dynamicDimPanel.add(colsLabel);
|
dynamicDimPanel.add(colsLabel);
|
||||||
dynamicDimPanel.add(dynamicColsSpinner);
|
dynamicDimPanel.add(dynamicColsSpinner);
|
||||||
mainPanel.add(dynamicDimPanel, gbc);
|
contentPanel.add(dynamicDimPanel, gbc);
|
||||||
|
|
||||||
Runnable updateDynamicVisibility = () -> {
|
Runnable updateDynamicVisibility = () -> {
|
||||||
TerminalModel m = (TerminalModel) modelCombo.getSelectedItem();
|
TerminalModel m = (TerminalModel) modelCombo.getSelectedItem();
|
||||||
@@ -125,40 +197,46 @@ public class ConnectDialog extends JDialog {
|
|||||||
modelCombo.addActionListener(e -> updateDynamicVisibility.run());
|
modelCombo.addActionListener(e -> updateDynamicVisibility.run());
|
||||||
updateDynamicVisibility.run();
|
updateDynamicVisibility.run();
|
||||||
|
|
||||||
// LU Name
|
gridy++;
|
||||||
|
|
||||||
|
// 5. LU Name
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 4;
|
gbc.gridy = gridy;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel luLabel = new JLabel("LU Name:");
|
JLabel luLabel = new JLabel("LU Name:");
|
||||||
luLabel.setFont(labelFont);
|
luLabel.setFont(labelFont);
|
||||||
mainPanel.add(luLabel, gbc);
|
contentPanel.add(luLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
luField = createField(12);
|
luField = createField(12);
|
||||||
mainPanel.add(luField, gbc);
|
contentPanel.add(luField, gbc);
|
||||||
|
|
||||||
// Graphics Mode
|
gridy++;
|
||||||
|
|
||||||
|
// 6. Graphics Mode
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 5;
|
gbc.gridy = gridy;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel graphicsLabel = new JLabel("Graphics:");
|
JLabel graphicsLabel = new JLabel("Graphics:");
|
||||||
graphicsLabel.setFont(labelFont);
|
graphicsLabel.setFont(labelFont);
|
||||||
mainPanel.add(graphicsLabel, gbc);
|
contentPanel.add(graphicsLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
graphicsCombo = new JComboBox<>(haus.nightmare.lib3270j.graphics.GraphicsMode.values());
|
graphicsCombo = new JComboBox<>(haus.nightmare.lib3270j.graphics.GraphicsMode.values());
|
||||||
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
graphicsCombo.setSelectedItem(haus.nightmare.j3270.config.Settings.getGraphicsMode());
|
||||||
graphicsCombo.setFont(ThemeManager.getUiFont());
|
graphicsCombo.setFont(ThemeManager.getUiFont());
|
||||||
ThemeManager.styleComboBox(graphicsCombo);
|
ThemeManager.styleComboBox(graphicsCombo);
|
||||||
mainPanel.add(graphicsCombo, gbc);
|
contentPanel.add(graphicsCombo, gbc);
|
||||||
|
|
||||||
// Code Page
|
gridy++;
|
||||||
|
|
||||||
|
// 7. Code Page
|
||||||
gbc.gridx = 0;
|
gbc.gridx = 0;
|
||||||
gbc.gridy = 6;
|
gbc.gridy = gridy;
|
||||||
gbc.weightx = 0;
|
gbc.weightx = 0;
|
||||||
JLabel cpLabel = new JLabel("Code Page:");
|
JLabel cpLabel = new JLabel("Code Page:");
|
||||||
cpLabel.setFont(labelFont);
|
cpLabel.setFont(labelFont);
|
||||||
mainPanel.add(cpLabel, gbc);
|
contentPanel.add(cpLabel, gbc);
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.weightx = 1.0;
|
gbc.weightx = 1.0;
|
||||||
String[] commonCodePages = {
|
String[] commonCodePages = {
|
||||||
@@ -179,43 +257,44 @@ public class ConnectDialog extends JDialog {
|
|||||||
"1155 - Turkey (Turkish Latin-5 Euro \u20AC)",
|
"1155 - Turkey (Turkish Latin-5 Euro \u20AC)",
|
||||||
"905 - Turkey (Turkish Latin-3)",
|
"905 - Turkey (Turkish Latin-3)",
|
||||||
"420 - Arabic Bilingual",
|
"420 - Arabic Bilingual",
|
||||||
"424 - Hebrew (with Lowercase)",
|
"424 - Hebrew (with Latin)",
|
||||||
"803 - Hebrew Old / Standard",
|
"803 - Hebrew Character Set",
|
||||||
"838 - Thai",
|
"838 - Thai Extended",
|
||||||
"1160 - Thai (Euro \u20AC)",
|
"1160 - Thai Euro \u20AC",
|
||||||
"1025 - Cyrillic Multilingual",
|
"1025 - Cyrillic Multilingual",
|
||||||
"1123 - Cyrillic Ukraine",
|
"1123 - Cyrillic Ukraine",
|
||||||
"1154 - Cyrillic Multilingual (Euro \u20AC)",
|
"1154 - Cyrillic Euro \u20AC",
|
||||||
"880 - Cyrillic Russian",
|
"880 - Cyrillic (Russian)",
|
||||||
"1140 - US / Canada (Euro \u20AC)",
|
"1140 - US / Canada (Euro \u20AC)",
|
||||||
"1141 - Germany / Austria (Euro \u20AC)",
|
"1141 - Germany / Austria (Euro \u20AC)",
|
||||||
|
"1142 - Denmark / Norway (Euro \u20AC)",
|
||||||
|
"1143 - Sweden / Finland (Euro \u20AC)",
|
||||||
|
"1144 - Italy (Euro \u20AC)",
|
||||||
|
"1145 - Spain / Latin America (Euro \u20AC)",
|
||||||
|
"1146 - United Kingdom (Euro \u20AC)",
|
||||||
|
"1147 - France (Euro \u20AC)",
|
||||||
"1148 - International (Euro \u20AC)",
|
"1148 - International (Euro \u20AC)",
|
||||||
|
"1149 - Iceland (Euro \u20AC)",
|
||||||
"930 - Japanese Katakana Mixed DBCS",
|
"930 - Japanese Katakana Mixed DBCS",
|
||||||
"939 - Japanese Latin Mixed DBCS",
|
"939 - Japanese Latin Mixed DBCS",
|
||||||
"935 - Simplified Chinese Mixed DBCS",
|
"935 - Simplified Chinese Mixed DBCS",
|
||||||
"1388 - Simplified Chinese Extended Mixed DBCS",
|
|
||||||
"937 - Traditional Chinese Mixed DBCS",
|
"937 - Traditional Chinese Mixed DBCS",
|
||||||
"1371 - Traditional Chinese Extended Mixed DBCS",
|
"1388 - Simplified Chinese Extended DBCS",
|
||||||
|
"1371 - Traditional Chinese Extended DBCS",
|
||||||
"933 - Korean Mixed DBCS"
|
"933 - Korean Mixed DBCS"
|
||||||
};
|
};
|
||||||
codePageCombo = new JComboBox<>(commonCodePages);
|
codePageCombo = new JComboBox<>(commonCodePages);
|
||||||
codePageCombo.setEditable(true);
|
codePageCombo.setFont(ThemeManager.getMonospacedUiFont());
|
||||||
String currentCp = haus.nightmare.j3270.config.Settings.getCodePage();
|
|
||||||
for (String item : commonCodePages) {
|
|
||||||
if (item.startsWith(currentCp + " ") || item.equals(currentCp)) {
|
|
||||||
codePageCombo.setSelectedItem(item);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
codePageCombo.setFont(ThemeManager.getUiFont());
|
|
||||||
ThemeManager.styleComboBox(codePageCombo);
|
ThemeManager.styleComboBox(codePageCombo);
|
||||||
mainPanel.add(codePageCombo, gbc);
|
setInitialCodePage(haus.nightmare.j3270.config.Settings.getCodePage());
|
||||||
|
contentPanel.add(codePageCombo, gbc);
|
||||||
|
|
||||||
// TLS / SSL Checkbox
|
gridy++;
|
||||||
|
|
||||||
|
// 8. TLS Checkbox
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.gridy = 7;
|
gbc.gridy = gridy;
|
||||||
gbc.weightx = 1.0;
|
tlsCheckBox = new JCheckBox("Use TLS / SSL Encryption");
|
||||||
tlsCheckBox = new JCheckBox("Enable TLS/SSL");
|
|
||||||
ThemeManager.styleCheckBox(tlsCheckBox);
|
ThemeManager.styleCheckBox(tlsCheckBox);
|
||||||
tlsCheckBox.setFont(labelFont);
|
tlsCheckBox.setFont(labelFont);
|
||||||
tlsCheckBox.addActionListener(e -> {
|
tlsCheckBox.addActionListener(e -> {
|
||||||
@@ -228,46 +307,169 @@ public class ConnectDialog extends JDialog {
|
|||||||
portField.setText("23");
|
portField.setText("23");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mainPanel.add(tlsCheckBox, gbc);
|
contentPanel.add(tlsCheckBox, gbc);
|
||||||
|
|
||||||
// Verify Certificate Checkbox
|
gridy++;
|
||||||
|
|
||||||
|
// 9. Verify Cert Checkbox
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.gridy = 8;
|
gbc.gridy = gridy;
|
||||||
verifyCertCheckBox = new JCheckBox("Verify Server Certificate");
|
verifyCertCheckBox = new JCheckBox("Verify Server Certificate");
|
||||||
ThemeManager.styleCheckBox(verifyCertCheckBox);
|
ThemeManager.styleCheckBox(verifyCertCheckBox);
|
||||||
verifyCertCheckBox.setFont(ThemeManager.getUiFont());
|
verifyCertCheckBox.setFont(ThemeManager.getUiFont());
|
||||||
verifyCertCheckBox.setSelected(true);
|
verifyCertCheckBox.setSelected(true);
|
||||||
verifyCertCheckBox.setEnabled(false);
|
verifyCertCheckBox.setEnabled(false);
|
||||||
mainPanel.add(verifyCertCheckBox, gbc);
|
contentPanel.add(verifyCertCheckBox, gbc);
|
||||||
|
|
||||||
// TN3270E Checkbox
|
gridy++;
|
||||||
|
|
||||||
|
// 10. TN3270E Checkbox
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.gridy = 9;
|
gbc.gridy = gridy;
|
||||||
tn3270eCheckBox = new JCheckBox("Enable TN3270E (Extended 3270)");
|
tn3270eCheckBox = new JCheckBox("Enable TN3270E (Extended 3270)");
|
||||||
ThemeManager.styleCheckBox(tn3270eCheckBox);
|
ThemeManager.styleCheckBox(tn3270eCheckBox);
|
||||||
tn3270eCheckBox.setFont(ThemeManager.getUiFont());
|
tn3270eCheckBox.setFont(ThemeManager.getUiFont());
|
||||||
tn3270eCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectTn3270e());
|
tn3270eCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectTn3270e());
|
||||||
mainPanel.add(tn3270eCheckBox, gbc);
|
contentPanel.add(tn3270eCheckBox, gbc);
|
||||||
|
|
||||||
// Keep-Alive Checkbox
|
gridy++;
|
||||||
|
|
||||||
|
// 11. Keep-Alive Checkbox
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.gridy = 10;
|
gbc.gridy = gridy;
|
||||||
keepAliveCheckBox = new JCheckBox("Enable Keep-Alive Heartbeat (NOP)");
|
keepAliveCheckBox = new JCheckBox("Enable Keep-Alive Heartbeat (NOP)");
|
||||||
ThemeManager.styleCheckBox(keepAliveCheckBox);
|
ThemeManager.styleCheckBox(keepAliveCheckBox);
|
||||||
keepAliveCheckBox.setFont(ThemeManager.getUiFont());
|
keepAliveCheckBox.setFont(ThemeManager.getUiFont());
|
||||||
keepAliveCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectKeepAlive());
|
keepAliveCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectKeepAlive());
|
||||||
mainPanel.add(keepAliveCheckBox, gbc);
|
contentPanel.add(keepAliveCheckBox, gbc);
|
||||||
|
|
||||||
// Auto-Reconnect Checkbox
|
gridy++;
|
||||||
|
|
||||||
|
// 12. Auto-Reconnect Checkbox
|
||||||
gbc.gridx = 1;
|
gbc.gridx = 1;
|
||||||
gbc.gridy = 11;
|
gbc.gridy = gridy;
|
||||||
autoReconnectCheckBox = new JCheckBox("Auto-Reconnect on Disconnect");
|
autoReconnectCheckBox = new JCheckBox("Auto-Reconnect on Disconnect");
|
||||||
ThemeManager.styleCheckBox(autoReconnectCheckBox);
|
ThemeManager.styleCheckBox(autoReconnectCheckBox);
|
||||||
autoReconnectCheckBox.setFont(ThemeManager.getUiFont());
|
autoReconnectCheckBox.setFont(ThemeManager.getUiFont());
|
||||||
autoReconnectCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectAutoReconnect());
|
autoReconnectCheckBox.setSelected(haus.nightmare.j3270.config.Settings.getAutoConnectAutoReconnect());
|
||||||
mainPanel.add(autoReconnectCheckBox, gbc);
|
contentPanel.add(autoReconnectCheckBox, gbc);
|
||||||
|
|
||||||
|
gridy++;
|
||||||
|
|
||||||
|
// 13. PER-CONNECTION PROXY CONFIGURATION PANEL
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = gridy;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
|
||||||
|
JPanel proxyGroup = new JPanel(new GridBagLayout());
|
||||||
|
proxyGroup.setBorder(BorderFactory.createTitledBorder(
|
||||||
|
BorderFactory.createEtchedBorder(),
|
||||||
|
"Proxy Configuration (This Connection)",
|
||||||
|
TitledBorder.LEFT,
|
||||||
|
TitledBorder.TOP,
|
||||||
|
labelFont
|
||||||
|
));
|
||||||
|
GridBagConstraints pgbc = new GridBagConstraints();
|
||||||
|
pgbc.insets = new Insets(3, 6, 3, 6);
|
||||||
|
pgbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
// Proxy Type
|
||||||
|
pgbc.gridx = 0;
|
||||||
|
pgbc.gridy = 0;
|
||||||
|
pgbc.weightx = 0;
|
||||||
|
JLabel ptLabel = new JLabel("Proxy Type:");
|
||||||
|
ptLabel.setFont(labelFont);
|
||||||
|
proxyGroup.add(ptLabel, pgbc);
|
||||||
|
|
||||||
|
pgbc.gridx = 1;
|
||||||
|
pgbc.gridwidth = 3;
|
||||||
|
pgbc.weightx = 1.0;
|
||||||
|
proxyTypeCombo = new JComboBox<>(ConnectionConfig.ProxyType.values());
|
||||||
|
proxyTypeCombo.setSelectedItem(ConnectionConfig.ProxyType.NONE);
|
||||||
|
proxyTypeCombo.setFont(ThemeManager.getUiFont());
|
||||||
|
ThemeManager.styleComboBox(proxyTypeCombo);
|
||||||
|
proxyGroup.add(proxyTypeCombo, pgbc);
|
||||||
|
|
||||||
|
// Proxy Host & Port
|
||||||
|
pgbc.gridx = 0;
|
||||||
|
pgbc.gridy = 1;
|
||||||
|
pgbc.gridwidth = 1;
|
||||||
|
pgbc.weightx = 0;
|
||||||
|
JLabel phLabel = new JLabel("Proxy Host:");
|
||||||
|
phLabel.setFont(labelFont);
|
||||||
|
proxyGroup.add(phLabel, pgbc);
|
||||||
|
|
||||||
|
pgbc.gridx = 1;
|
||||||
|
pgbc.weightx = 1.0;
|
||||||
|
proxyHostField = createField(14);
|
||||||
|
proxyGroup.add(proxyHostField, pgbc);
|
||||||
|
|
||||||
|
pgbc.gridx = 2;
|
||||||
|
pgbc.weightx = 0;
|
||||||
|
JLabel ppLabel = new JLabel("Port:");
|
||||||
|
ppLabel.setFont(labelFont);
|
||||||
|
proxyGroup.add(ppLabel, pgbc);
|
||||||
|
|
||||||
|
pgbc.gridx = 3;
|
||||||
|
pgbc.weightx = 0.4;
|
||||||
|
proxyPortField = createField(5);
|
||||||
|
proxyGroup.add(proxyPortField, pgbc);
|
||||||
|
|
||||||
|
// Proxy User & Password
|
||||||
|
pgbc.gridx = 0;
|
||||||
|
pgbc.gridy = 2;
|
||||||
|
pgbc.gridwidth = 1;
|
||||||
|
pgbc.weightx = 0;
|
||||||
|
JLabel puLabel = new JLabel("Username:");
|
||||||
|
puLabel.setFont(labelFont);
|
||||||
|
proxyGroup.add(puLabel, pgbc);
|
||||||
|
|
||||||
|
pgbc.gridx = 1;
|
||||||
|
pgbc.weightx = 1.0;
|
||||||
|
proxyUserField = createField(12);
|
||||||
|
proxyGroup.add(proxyUserField, pgbc);
|
||||||
|
|
||||||
|
pgbc.gridx = 2;
|
||||||
|
pgbc.weightx = 0;
|
||||||
|
JLabel pwLabel = new JLabel("Password:");
|
||||||
|
pwLabel.setFont(labelFont);
|
||||||
|
proxyGroup.add(pwLabel, pgbc);
|
||||||
|
|
||||||
|
pgbc.gridx = 3;
|
||||||
|
pgbc.weightx = 0.4;
|
||||||
|
proxyPassField = createPasswordField(10);
|
||||||
|
proxyGroup.add(proxyPassField, pgbc);
|
||||||
|
|
||||||
|
// Dynamic enable/disable based on Proxy Type
|
||||||
|
Runnable updateProxyFields = () -> {
|
||||||
|
ConnectionConfig.ProxyType pt = (ConnectionConfig.ProxyType) proxyTypeCombo.getSelectedItem();
|
||||||
|
boolean active = (pt != null && pt != ConnectionConfig.ProxyType.NONE);
|
||||||
|
proxyHostField.setEnabled(active);
|
||||||
|
proxyPortField.setEnabled(active);
|
||||||
|
proxyUserField.setEnabled(active);
|
||||||
|
proxyPassField.setEnabled(active && pt != ConnectionConfig.ProxyType.SOCKS4);
|
||||||
|
|
||||||
|
if (active) {
|
||||||
|
String currentPPort = proxyPortField.getText().trim();
|
||||||
|
if (currentPPort.isEmpty() || "0".equals(currentPPort)) {
|
||||||
|
proxyPortField.setText(pt == ConnectionConfig.ProxyType.HTTP ? "8080" : "1080");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
proxyTypeCombo.addActionListener(e -> updateProxyFields.run());
|
||||||
|
updateProxyFields.run();
|
||||||
|
|
||||||
|
contentPanel.add(proxyGroup, gbc);
|
||||||
|
|
||||||
|
gridy++;
|
||||||
|
|
||||||
|
// 14. Action Buttons
|
||||||
|
gbc.gridx = 0;
|
||||||
|
gbc.gridy = gridy;
|
||||||
|
gbc.gridwidth = 2;
|
||||||
|
gbc.weightx = 1.0;
|
||||||
|
|
||||||
// Buttons
|
|
||||||
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
|
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 8, 4));
|
||||||
|
|
||||||
JButton connectBtn = new JButton("Connect");
|
JButton connectBtn = new JButton("Connect");
|
||||||
@@ -285,13 +487,14 @@ public class ConnectDialog extends JDialog {
|
|||||||
|
|
||||||
buttonPanel.add(cancelBtn);
|
buttonPanel.add(cancelBtn);
|
||||||
buttonPanel.add(connectBtn);
|
buttonPanel.add(connectBtn);
|
||||||
|
contentPanel.add(buttonPanel, gbc);
|
||||||
|
|
||||||
gbc.gridx = 0;
|
JScrollPane scrollPane = new JScrollPane(contentPanel);
|
||||||
gbc.gridy = 12;
|
scrollPane.setBorder(BorderFactory.createEmptyBorder());
|
||||||
gbc.gridwidth = 2;
|
scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
mainPanel.add(buttonPanel, gbc);
|
scrollPane.getVerticalScrollBar().setUnitIncrement(14);
|
||||||
|
setContentPane(scrollPane);
|
||||||
|
|
||||||
setContentPane(mainPanel);
|
|
||||||
ThemeManager.applyThemeToWindow(this);
|
ThemeManager.applyThemeToWindow(this);
|
||||||
|
|
||||||
// Enter key triggers connect
|
// Enter key triggers connect
|
||||||
@@ -305,6 +508,189 @@ public class ConnectDialog extends JDialog {
|
|||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JPasswordField createPasswordField(int cols) {
|
||||||
|
JPasswordField field = new JPasswordField(cols);
|
||||||
|
field.setFont(ThemeManager.getMonospacedUiFont());
|
||||||
|
ThemeManager.styleTextField(field);
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= Profile Management =================
|
||||||
|
|
||||||
|
private void refreshProfileCombo() {
|
||||||
|
profileCombo.removeAllItems();
|
||||||
|
profileCombo.addItem(NEW_PROFILE_LABEL);
|
||||||
|
List<SavedHost> saved = HostStorage.getSavedHosts();
|
||||||
|
for (SavedHost h : saved) {
|
||||||
|
profileCombo.addItem(h);
|
||||||
|
}
|
||||||
|
if (selectedProfileId != null) {
|
||||||
|
for (int i = 0; i < profileCombo.getItemCount(); i++) {
|
||||||
|
Object item = profileCombo.getItemAt(i);
|
||||||
|
if (item instanceof SavedHost && ((SavedHost) item).getId().equals(selectedProfileId)) {
|
||||||
|
profileCombo.setSelectedIndex(i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
profileCombo.setSelectedIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onProfileSelectionChanged() {
|
||||||
|
Object sel = profileCombo.getSelectedItem();
|
||||||
|
if (sel instanceof SavedHost) {
|
||||||
|
SavedHost h = (SavedHost) sel;
|
||||||
|
selectedProfileId = h.getId();
|
||||||
|
deleteProfileBtn.setEnabled(true);
|
||||||
|
loadProfileFields(h);
|
||||||
|
} else {
|
||||||
|
selectedProfileId = null;
|
||||||
|
deleteProfileBtn.setEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadProfile(SavedHost h) {
|
||||||
|
if (h == null) return;
|
||||||
|
selectedProfileId = h.getId();
|
||||||
|
refreshProfileCombo();
|
||||||
|
loadProfileFields(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadProfileFields(SavedHost h) {
|
||||||
|
if (h == null) return;
|
||||||
|
hostField.setText(h.getHost());
|
||||||
|
portField.setText(String.valueOf(h.getPort()));
|
||||||
|
if (h.getModel() == 0) {
|
||||||
|
modelCombo.setSelectedItem(TerminalModel.IBM_DYNAMIC);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
modelCombo.setSelectedItem(TerminalModel.forModel(h.getModel(), true));
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamicRowsSpinner.setValue(h.getDynamicRows() > 0 ? h.getDynamicRows() : 62);
|
||||||
|
dynamicColsSpinner.setValue(h.getDynamicCols() > 0 ? h.getDynamicCols() : 160);
|
||||||
|
luField.setText(h.getLuName() != null ? h.getLuName() : "");
|
||||||
|
tlsCheckBox.setSelected(h.isUseTls());
|
||||||
|
verifyCertCheckBox.setEnabled(h.isUseTls());
|
||||||
|
verifyCertCheckBox.setSelected(h.isTlsVerifyCert());
|
||||||
|
tn3270eCheckBox.setSelected(h.isTn3270e());
|
||||||
|
setInitialCodePage(h.getCodePage());
|
||||||
|
|
||||||
|
if (h.getGraphicsMode() != null) {
|
||||||
|
graphicsCombo.setSelectedItem(haus.nightmare.lib3270j.graphics.GraphicsMode.fromString(h.getGraphicsMode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-connection proxy parameters
|
||||||
|
String ptStr = h.getProxyType();
|
||||||
|
try {
|
||||||
|
ConnectionConfig.ProxyType pt = (ptStr != null && !ptStr.trim().isEmpty())
|
||||||
|
? ConnectionConfig.ProxyType.valueOf(ptStr.toUpperCase())
|
||||||
|
: ConnectionConfig.ProxyType.NONE;
|
||||||
|
proxyTypeCombo.setSelectedItem(pt);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
proxyTypeCombo.setSelectedItem(ConnectionConfig.ProxyType.NONE);
|
||||||
|
}
|
||||||
|
proxyHostField.setText(h.getProxyHost() != null ? h.getProxyHost() : "");
|
||||||
|
proxyPortField.setText(h.getProxyPort() > 0 ? String.valueOf(h.getProxyPort()) : "");
|
||||||
|
proxyUserField.setText(h.getProxyUsername() != null ? h.getProxyUsername() : "");
|
||||||
|
proxyPassField.setText(h.getProxyPassword() != null ? h.getProxyPassword() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSaveProfile() {
|
||||||
|
String hStr = hostField.getText().trim();
|
||||||
|
if (hStr.isEmpty()) {
|
||||||
|
hostField.requestFocus();
|
||||||
|
JOptionPane.showMessageDialog(this, "Please specify a host before saving profile.", "Missing Host", JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String defaultName = hStr;
|
||||||
|
Object currentSel = profileCombo.getSelectedItem();
|
||||||
|
if (currentSel instanceof SavedHost) {
|
||||||
|
defaultName = ((SavedHost) currentSel).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
String profileName = (String) JOptionPane.showInputDialog(
|
||||||
|
this,
|
||||||
|
"Enter Host Profile Name:",
|
||||||
|
"Save Profile",
|
||||||
|
JOptionPane.PLAIN_MESSAGE,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
defaultName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (profileName == null || profileName.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
profileName = profileName.trim();
|
||||||
|
|
||||||
|
SavedHost hostToSave = buildCurrentSavedHost(profileName);
|
||||||
|
HostStorage.saveHost(hostToSave);
|
||||||
|
selectedProfileId = hostToSave.getId();
|
||||||
|
refreshProfileCombo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDeleteProfile() {
|
||||||
|
if (selectedProfileId == null) return;
|
||||||
|
int opt = JOptionPane.showConfirmDialog(this,
|
||||||
|
"Are you sure you want to delete this host profile?",
|
||||||
|
"Delete Profile",
|
||||||
|
JOptionPane.YES_NO_OPTION);
|
||||||
|
if (opt == JOptionPane.YES_OPTION) {
|
||||||
|
HostStorage.deleteHost(selectedProfileId);
|
||||||
|
selectedProfileId = null;
|
||||||
|
refreshProfileCombo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SavedHost buildCurrentSavedHost(String profileName) {
|
||||||
|
String id = selectedProfileId != null ? selectedProfileId : java.util.UUID.randomUUID().toString();
|
||||||
|
String h = hostField.getText().trim();
|
||||||
|
int port = 23;
|
||||||
|
try {
|
||||||
|
port = Integer.parseInt(portField.getText().trim());
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
|
||||||
|
TerminalModel m = (TerminalModel) modelCombo.getSelectedItem();
|
||||||
|
int mNum = (m != null) ? m.getModelNumber() : 4;
|
||||||
|
int dRows = (Integer) dynamicRowsSpinner.getValue();
|
||||||
|
int dCols = (Integer) dynamicColsSpinner.getValue();
|
||||||
|
String lu = luField.getText().trim();
|
||||||
|
boolean tls = tlsCheckBox.isSelected();
|
||||||
|
boolean verify = verifyCertCheckBox.isSelected();
|
||||||
|
boolean tn3270e = tn3270eCheckBox.isSelected();
|
||||||
|
|
||||||
|
String gfx = "BOTH";
|
||||||
|
if (graphicsCombo.getSelectedItem() != null) {
|
||||||
|
gfx = graphicsCombo.getSelectedItem().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String cp = "037";
|
||||||
|
Object cpSel = codePageCombo.getSelectedItem();
|
||||||
|
if (cpSel != null) {
|
||||||
|
String s = cpSel.toString().trim();
|
||||||
|
int dash = s.indexOf(" -");
|
||||||
|
cp = (dash > 0) ? s.substring(0, dash).trim() : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionConfig.ProxyType pt = (ConnectionConfig.ProxyType) proxyTypeCombo.getSelectedItem();
|
||||||
|
String ptStr = (pt != null) ? pt.name() : "NONE";
|
||||||
|
String pHost = proxyHostField.getText().trim();
|
||||||
|
int pPort = 0;
|
||||||
|
try {
|
||||||
|
pPort = Integer.parseInt(proxyPortField.getText().trim());
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
String pUser = proxyUserField.getText().trim();
|
||||||
|
String pPass = new String(proxyPassField.getPassword());
|
||||||
|
|
||||||
|
return new SavedHost(id, profileName, h, port, mNum, dRows, dCols, lu, false,
|
||||||
|
"TSO", tls, verify, tn3270e, gfx, cp, ptStr, pHost, pPort, pUser, pPass);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= Connect Action =================
|
||||||
|
|
||||||
private void onConnect() {
|
private void onConnect() {
|
||||||
String host = hostField.getText().trim();
|
String host = hostField.getText().trim();
|
||||||
if (host.isEmpty()) {
|
if (host.isEmpty()) {
|
||||||
@@ -321,19 +707,32 @@ public class ConnectDialog extends JDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TerminalModel selectedModel = (TerminalModel) modelCombo.getSelectedItem();
|
TerminalModel selectedModel = (TerminalModel) modelCombo.getSelectedItem();
|
||||||
|
if (selectedModel == null) {
|
||||||
|
selectedModel = TerminalModel.IBM_3279_4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support full host string parsing if the user pasted flags or prefixes into host field
|
||||||
|
if (host.contains(" ") || host.startsWith("L:") || host.startsWith("P:") || host.startsWith("--proxy=") || host.startsWith("-proxy=")) {
|
||||||
|
result = ConnectionConfig.parseHostString(host, port, selectedModel);
|
||||||
|
} else {
|
||||||
result = new ConnectionConfig(host, port, selectedModel);
|
result = new ConnectionConfig(host, port, selectedModel);
|
||||||
if (selectedModel != null && selectedModel.isDynamic()) {
|
}
|
||||||
|
|
||||||
|
if (selectedModel.isDynamic()) {
|
||||||
int dRows = (Integer) dynamicRowsSpinner.getValue();
|
int dRows = (Integer) dynamicRowsSpinner.getValue();
|
||||||
int dCols = (Integer) dynamicColsSpinner.getValue();
|
int dCols = (Integer) dynamicColsSpinner.getValue();
|
||||||
result.setDynamicDimensions(dRows, dCols);
|
result.setDynamicDimensions(dRows, dCols);
|
||||||
haus.nightmare.j3270.config.Settings.setDynamicRows(dRows);
|
haus.nightmare.j3270.config.Settings.setDynamicRows(dRows);
|
||||||
haus.nightmare.j3270.config.Settings.setDynamicCols(dCols);
|
haus.nightmare.j3270.config.Settings.setDynamicCols(dCols);
|
||||||
}
|
}
|
||||||
|
|
||||||
String lu = luField.getText().trim();
|
String lu = luField.getText().trim();
|
||||||
if (!lu.isEmpty()) {
|
if (!lu.isEmpty()) {
|
||||||
result.setLuName(lu);
|
result.setLuName(lu);
|
||||||
}
|
}
|
||||||
|
if (graphicsCombo.getSelectedItem() != null) {
|
||||||
result.setGraphicsMode((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem());
|
result.setGraphicsMode((haus.nightmare.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem());
|
||||||
|
}
|
||||||
|
|
||||||
Object cpSelection = codePageCombo.getSelectedItem();
|
Object cpSelection = codePageCombo.getSelectedItem();
|
||||||
if (cpSelection != null) {
|
if (cpSelection != null) {
|
||||||
@@ -361,6 +760,22 @@ public class ConnectDialog extends JDialog {
|
|||||||
result.setReconnectMaxRetries(haus.nightmare.j3270.config.Settings.getAutoConnectReconnectMaxRetries());
|
result.setReconnectMaxRetries(haus.nightmare.j3270.config.Settings.getAutoConnectReconnectMaxRetries());
|
||||||
haus.nightmare.j3270.config.Settings.setAutoConnectAutoReconnect(ar);
|
haus.nightmare.j3270.config.Settings.setAutoConnectAutoReconnect(ar);
|
||||||
|
|
||||||
|
// Apply per-connection proxy parameters strictly to this connection
|
||||||
|
ConnectionConfig.ProxyType pType = (ConnectionConfig.ProxyType) proxyTypeCombo.getSelectedItem();
|
||||||
|
String pHost = proxyHostField.getText().trim();
|
||||||
|
if (pType != null && pType != ConnectionConfig.ProxyType.NONE && !pHost.isEmpty()) {
|
||||||
|
int pPort = 0;
|
||||||
|
try {
|
||||||
|
pPort = Integer.parseInt(proxyPortField.getText().trim());
|
||||||
|
} catch (NumberFormatException ignored) {}
|
||||||
|
if (pPort <= 0) {
|
||||||
|
pPort = (pType == ConnectionConfig.ProxyType.HTTP) ? 8080 : 1080;
|
||||||
|
}
|
||||||
|
String pUser = proxyUserField.getText().trim();
|
||||||
|
String pPass = new String(proxyPassField.getPassword());
|
||||||
|
result.setProxy(pType, pHost, pPort, pUser.isEmpty() ? null : pUser, pPass.isEmpty() ? null : pPass);
|
||||||
|
}
|
||||||
|
|
||||||
confirmed = true;
|
confirmed = true;
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
@@ -407,4 +822,49 @@ public class ConnectDialog extends JDialog {
|
|||||||
codePageCombo.setSelectedItem(cp);
|
codePageCombo.setSelectedItem(cp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set initial proxy settings for this connection.
|
||||||
|
*/
|
||||||
|
public void setInitialProxy(ConnectionConfig.ProxyType type, String host, int port, String user, String pass) {
|
||||||
|
if (proxyTypeCombo != null) {
|
||||||
|
proxyTypeCombo.setSelectedItem(type != null ? type : ConnectionConfig.ProxyType.NONE);
|
||||||
|
}
|
||||||
|
if (proxyHostField != null) {
|
||||||
|
proxyHostField.setText(host != null ? host : "");
|
||||||
|
}
|
||||||
|
if (proxyPortField != null) {
|
||||||
|
proxyPortField.setText(port > 0 ? String.valueOf(port) : "");
|
||||||
|
}
|
||||||
|
if (proxyUserField != null) {
|
||||||
|
proxyUserField.setText(user != null ? user : "");
|
||||||
|
}
|
||||||
|
if (proxyPassField != null) {
|
||||||
|
proxyPassField.setText(pass != null ? pass : "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionConfig.ProxyType getSelectedProxyType() {
|
||||||
|
return (ConnectionConfig.ProxyType) proxyTypeCombo.getSelectedItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProxyHost() {
|
||||||
|
return proxyHostField.getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProxyPort() {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(proxyPortField.getText().trim());
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProxyUsername() {
|
||||||
|
return proxyUserField.getText().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProxyPassword() {
|
||||||
|
return new String(proxyPassField.getPassword());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,307 @@
|
|||||||
|
package haus.nightmare.j3270.storage;
|
||||||
|
|
||||||
|
import haus.nightmare.j3270.ui.ConnectDialog;
|
||||||
|
import haus.nightmare.lib3270j.ConnectionConfig;
|
||||||
|
import haus.nightmare.lib3270j.TerminalModel;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.awt.Frame;
|
||||||
|
import java.awt.GraphicsEnvironment;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class HostStorageAndProxyTest {
|
||||||
|
|
||||||
|
private List<SavedHost> originalHosts;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
originalHosts = HostStorage.getSavedHosts();
|
||||||
|
HostStorage.clearAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void tearDown() {
|
||||||
|
HostStorage.clearAll();
|
||||||
|
HostStorage.saveAll(originalHosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("SavedHost correctly models per-connection proxy and converts to ConnectionConfig")
|
||||||
|
public void testSavedHostProxyToConnectionConfig() {
|
||||||
|
// 1. Host with SOCKS5 proxy and auth
|
||||||
|
SavedHost host1 = new SavedHost(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
"Prod Mainframe",
|
||||||
|
"mvs.corp.local",
|
||||||
|
2323,
|
||||||
|
4,
|
||||||
|
43,
|
||||||
|
80,
|
||||||
|
"TSO01",
|
||||||
|
false,
|
||||||
|
"TSO",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"BOTH",
|
||||||
|
"037",
|
||||||
|
"SOCKS5",
|
||||||
|
"proxy.internal.corp",
|
||||||
|
1080,
|
||||||
|
"admin",
|
||||||
|
"secret123"
|
||||||
|
);
|
||||||
|
|
||||||
|
ConnectionConfig cfg1 = host1.toConnectionConfig();
|
||||||
|
assertEquals("mvs.corp.local", cfg1.getHost());
|
||||||
|
assertEquals(2323, cfg1.getPort());
|
||||||
|
assertTrue(cfg1.isUseTls());
|
||||||
|
assertEquals("TSO01", cfg1.getLuName());
|
||||||
|
assertEquals(ConnectionConfig.ProxyType.SOCKS5, cfg1.getProxyType());
|
||||||
|
assertEquals("proxy.internal.corp", cfg1.getProxyHost());
|
||||||
|
assertEquals(1080, cfg1.getProxyPort());
|
||||||
|
assertEquals("admin", cfg1.getProxyUsername());
|
||||||
|
assertEquals("secret123", cfg1.getProxyPassword());
|
||||||
|
|
||||||
|
// 2. Host with direct connection (NONE proxy)
|
||||||
|
SavedHost host2 = new SavedHost(
|
||||||
|
UUID.randomUUID().toString(),
|
||||||
|
"Local VM370",
|
||||||
|
"127.0.0.1",
|
||||||
|
23,
|
||||||
|
2,
|
||||||
|
24,
|
||||||
|
80,
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
"CMS",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"BOTH",
|
||||||
|
"037",
|
||||||
|
"NONE",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
ConnectionConfig cfg2 = host2.toConnectionConfig();
|
||||||
|
assertEquals("127.0.0.1", cfg2.getHost());
|
||||||
|
assertEquals(23, cfg2.getPort());
|
||||||
|
assertFalse(cfg2.isUseTls());
|
||||||
|
assertEquals(ConnectionConfig.ProxyType.NONE, cfg2.getProxyType());
|
||||||
|
assertNull(cfg2.getProxyHost());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HostStorage persists and restores multiple host profiles with isolated per-connection proxies")
|
||||||
|
public void testHostStoragePersistenceAndIsolation() {
|
||||||
|
SavedHost h1 = new SavedHost(
|
||||||
|
"id-1",
|
||||||
|
"System A (SOCKS5)",
|
||||||
|
"sysa.corp",
|
||||||
|
23,
|
||||||
|
4,
|
||||||
|
62,
|
||||||
|
160,
|
||||||
|
"",
|
||||||
|
false,
|
||||||
|
"TSO",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"BOTH",
|
||||||
|
"037",
|
||||||
|
"SOCKS5",
|
||||||
|
"socks.gateway.net",
|
||||||
|
1080,
|
||||||
|
"alice",
|
||||||
|
"passA"
|
||||||
|
);
|
||||||
|
|
||||||
|
SavedHost h2 = new SavedHost(
|
||||||
|
"id-2",
|
||||||
|
"System B (HTTP CONNECT)",
|
||||||
|
"sysb.dmz",
|
||||||
|
992,
|
||||||
|
5,
|
||||||
|
27,
|
||||||
|
132,
|
||||||
|
"LU99",
|
||||||
|
false,
|
||||||
|
"TSO",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"BOTH",
|
||||||
|
"1047",
|
||||||
|
"HTTP",
|
||||||
|
"squid.proxy.org",
|
||||||
|
3128,
|
||||||
|
"bob",
|
||||||
|
"passB"
|
||||||
|
);
|
||||||
|
|
||||||
|
SavedHost h3 = new SavedHost(
|
||||||
|
"id-3",
|
||||||
|
"System C (Direct)",
|
||||||
|
"sysc.lan",
|
||||||
|
23,
|
||||||
|
2,
|
||||||
|
24,
|
||||||
|
80,
|
||||||
|
"",
|
||||||
|
true, // autoConnect
|
||||||
|
"CMS",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"BOTH",
|
||||||
|
"500",
|
||||||
|
"NONE",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
HostStorage.saveHost(h1);
|
||||||
|
HostStorage.saveHost(h2);
|
||||||
|
HostStorage.saveHost(h3);
|
||||||
|
|
||||||
|
List<SavedHost> loaded = HostStorage.getSavedHosts();
|
||||||
|
assertEquals(3, loaded.size());
|
||||||
|
|
||||||
|
SavedHost loadedA = HostStorage.findById("id-1");
|
||||||
|
assertNotNull(loadedA);
|
||||||
|
assertEquals("System A (SOCKS5)", loadedA.getName());
|
||||||
|
assertEquals("SOCKS5", loadedA.getProxyType());
|
||||||
|
assertEquals("socks.gateway.net", loadedA.getProxyHost());
|
||||||
|
assertEquals(1080, loadedA.getProxyPort());
|
||||||
|
assertEquals("alice", loadedA.getProxyUsername());
|
||||||
|
assertEquals("passA", loadedA.getProxyPassword());
|
||||||
|
|
||||||
|
SavedHost loadedB = HostStorage.findById("id-2");
|
||||||
|
assertNotNull(loadedB);
|
||||||
|
assertEquals("HTTP", loadedB.getProxyType());
|
||||||
|
assertEquals("squid.proxy.org", loadedB.getProxyHost());
|
||||||
|
assertEquals(3128, loadedB.getProxyPort());
|
||||||
|
assertEquals("bob", loadedB.getProxyUsername());
|
||||||
|
assertEquals("passB", loadedB.getProxyPassword());
|
||||||
|
|
||||||
|
SavedHost loadedC = HostStorage.findById("id-3");
|
||||||
|
assertNotNull(loadedC);
|
||||||
|
assertEquals("NONE", loadedC.getProxyType());
|
||||||
|
assertEquals("", loadedC.getProxyHost());
|
||||||
|
assertTrue(loadedC.isAutoConnect());
|
||||||
|
|
||||||
|
// Verify getAutoConnectHost returns System C with its own connection settings
|
||||||
|
SavedHost auto = HostStorage.getAutoConnectHost();
|
||||||
|
assertNotNull(auto);
|
||||||
|
assertEquals("id-3", auto.getId());
|
||||||
|
assertEquals(ConnectionConfig.ProxyType.NONE, auto.toConnectionConfig().getProxyType());
|
||||||
|
|
||||||
|
// Verify deleteHost
|
||||||
|
HostStorage.deleteHost("id-2");
|
||||||
|
assertEquals(2, HostStorage.getSavedHosts().size());
|
||||||
|
assertNull(HostStorage.findById("id-2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("HostStorage JSON matches a3270 format and parses a3270 JSON strings")
|
||||||
|
public void testA3270JsonFormatCompatibility() {
|
||||||
|
// String representative of a3270 saved_hosts preference JSON
|
||||||
|
String a3270Json = "[\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"id\": \"android-uuid-1\",\n" +
|
||||||
|
" \"name\": \"Dallas Mainframe\",\n" +
|
||||||
|
" \"host\": \"dallas.ibm.com\",\n" +
|
||||||
|
" \"port\": 2323,\n" +
|
||||||
|
" \"model\": 4,\n" +
|
||||||
|
" \"dynamicRows\": 62,\n" +
|
||||||
|
" \"dynamicCols\": 160,\n" +
|
||||||
|
" \"luName\": \"DAL01\",\n" +
|
||||||
|
" \"autoConnect\": false,\n" +
|
||||||
|
" \"hostType\": \"TSO\",\n" +
|
||||||
|
" \"useTls\": true,\n" +
|
||||||
|
" \"tlsVerifyCert\": false,\n" +
|
||||||
|
" \"tn3270e\": true,\n" +
|
||||||
|
" \"graphicsMode\": \"BOTH\",\n" +
|
||||||
|
" \"codePage\": \"037\",\n" +
|
||||||
|
" \"proxyType\": \"SOCKS5\",\n" +
|
||||||
|
" \"proxyHost\": \"10.20.30.40\",\n" +
|
||||||
|
" \"proxyPort\": 9050,\n" +
|
||||||
|
" \"proxyUsername\": \"dallasuser\",\n" +
|
||||||
|
" \"proxyPassword\": \"dallaspass\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
"]";
|
||||||
|
|
||||||
|
List<SavedHost> parsed = HostStorage.parseHostsJson(a3270Json);
|
||||||
|
assertEquals(1, parsed.size());
|
||||||
|
SavedHost h = parsed.get(0);
|
||||||
|
assertEquals("android-uuid-1", h.getId());
|
||||||
|
assertEquals("Dallas Mainframe", h.getName());
|
||||||
|
assertEquals("dallas.ibm.com", h.getHost());
|
||||||
|
assertEquals(2323, h.getPort());
|
||||||
|
assertEquals(4, h.getModel());
|
||||||
|
assertTrue(h.isUseTls());
|
||||||
|
assertFalse(h.isTlsVerifyCert());
|
||||||
|
assertEquals("SOCKS5", h.getProxyType());
|
||||||
|
assertEquals("10.20.30.40", h.getProxyHost());
|
||||||
|
assertEquals(9050, h.getProxyPort());
|
||||||
|
assertEquals("dallasuser", h.getProxyUsername());
|
||||||
|
assertEquals("dallaspass", h.getProxyPassword());
|
||||||
|
|
||||||
|
// Roundtrip serialization
|
||||||
|
String serialized = HostStorage.serializeHostsJson(parsed);
|
||||||
|
List<SavedHost> roundtripped = HostStorage.parseHostsJson(serialized);
|
||||||
|
assertEquals(1, roundtripped.size());
|
||||||
|
SavedHost r = roundtripped.get(0);
|
||||||
|
assertEquals(h.getId(), r.getId());
|
||||||
|
assertEquals(h.getProxyType(), r.getProxyType());
|
||||||
|
assertEquals(h.getProxyHost(), r.getProxyHost());
|
||||||
|
assertEquals(h.getProxyPort(), r.getProxyPort());
|
||||||
|
assertEquals(h.getProxyUsername(), r.getProxyUsername());
|
||||||
|
assertEquals(h.getProxyPassword(), r.getProxyPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("ConnectDialog loads profile proxy and applies per-connection proxy to ConnectionConfig")
|
||||||
|
public void testConnectDialogProxyHandling() {
|
||||||
|
if (!GraphicsEnvironment.isHeadless()) {
|
||||||
|
ConnectDialog dialog = new ConnectDialog((Frame) null);
|
||||||
|
dialog.setInitialHost("test.mainframe.com");
|
||||||
|
dialog.setInitialPort(23);
|
||||||
|
dialog.setInitialProxy(ConnectionConfig.ProxyType.SOCKS5, "127.0.0.1", 1080, "testuser", "testpass");
|
||||||
|
|
||||||
|
assertEquals(ConnectionConfig.ProxyType.SOCKS5, dialog.getSelectedProxyType());
|
||||||
|
assertEquals("127.0.0.1", dialog.getProxyHost());
|
||||||
|
assertEquals(1080, dialog.getProxyPort());
|
||||||
|
assertEquals("testuser", dialog.getProxyUsername());
|
||||||
|
assertEquals("testpass", dialog.getProxyPassword());
|
||||||
|
|
||||||
|
// Loading a profile updates dialog proxy fields
|
||||||
|
SavedHost profile = new SavedHost(
|
||||||
|
"p1", "Profile 1", "prod.ibm.net", 992, 4, 43, 80, "", false,
|
||||||
|
"TSO", true, true, true, "BOTH", "037",
|
||||||
|
"HTTP", "proxy.corp.net", 8080, "corpuser", "corppass"
|
||||||
|
);
|
||||||
|
dialog.loadProfile(profile);
|
||||||
|
|
||||||
|
assertEquals(ConnectionConfig.ProxyType.HTTP, dialog.getSelectedProxyType());
|
||||||
|
assertEquals("proxy.corp.net", dialog.getProxyHost());
|
||||||
|
assertEquals(8080, dialog.getProxyPort());
|
||||||
|
assertEquals("corpuser", dialog.getProxyUsername());
|
||||||
|
assertEquals("corppass", dialog.getProxyPassword());
|
||||||
|
dialog.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -548,6 +548,8 @@ public class DataStreamProcessor {
|
|||||||
applyExtendedAttribute(ea, attrType, attrValue);
|
applyExtendedAttribute(ea, attrType, attrValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
screen.setBufferAddress(baddr);
|
||||||
pos += 2 + nPairs * 2;
|
pos += 2 + nPairs * 2;
|
||||||
lastWasOrder = true;
|
lastWasOrder = true;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+73
@@ -198,4 +198,77 @@ public class DataStreamProcessorTest {
|
|||||||
processor.processRecord(ewStream.toByteArray(), 0, ewStream.size(), true);
|
processor.processRecord(ewStream.toByteArray(), 0, ewStream.size(), true);
|
||||||
assertEquals((byte) 0, screen.getCell(0).fg);
|
assertEquals((byte) 0, screen.getCell(0).fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyFieldAdvancesBufferAddressAndPreservesFieldAttributes() throws java.io.IOException {
|
||||||
|
// Step 1: Initialize screen with an Erase/Write defining two fields
|
||||||
|
java.io.ByteArrayOutputStream initStream = new java.io.ByteArrayOutputStream();
|
||||||
|
initStream.write(CMD_EW);
|
||||||
|
initStream.write(0xC3);
|
||||||
|
// Field 1 at pos 0: SFE with FA_PRINTABLE, text "ABC"
|
||||||
|
initStream.write(ORDER_SBA);
|
||||||
|
initStream.write(0x40); initStream.write(0x40); // pos 0
|
||||||
|
initStream.write(ORDER_SFE);
|
||||||
|
initStream.write(0x01); // 1 pair
|
||||||
|
initStream.write(XA_3270);
|
||||||
|
initStream.write(FA_PRINTABLE);
|
||||||
|
initStream.write(translator.stringToEbcdic("ABC"));
|
||||||
|
|
||||||
|
// Field 2 at pos 10: SFE with FA_PRINTABLE, text "XYZ"
|
||||||
|
initStream.write(ORDER_SBA);
|
||||||
|
byte[] addr10 = encodeAddress(10, screen.getRows(), screen.getCols());
|
||||||
|
initStream.write(addr10);
|
||||||
|
initStream.write(ORDER_SFE);
|
||||||
|
initStream.write(0x01);
|
||||||
|
initStream.write(XA_3270);
|
||||||
|
initStream.write(FA_PRINTABLE);
|
||||||
|
initStream.write(translator.stringToEbcdic("XYZ"));
|
||||||
|
|
||||||
|
processor.processRecord(initStream.toByteArray(), 0, initStream.size(), true);
|
||||||
|
|
||||||
|
assertTrue(screen.getCell(0).isFieldAttribute());
|
||||||
|
assertEquals('A', screen.getCell(1).ucs4);
|
||||||
|
assertTrue(screen.getCell(10).isFieldAttribute());
|
||||||
|
assertEquals('X', screen.getCell(11).ucs4);
|
||||||
|
|
||||||
|
// Step 2: Issue a Write command that uses ORDER_MF on Field 1 and Field 2
|
||||||
|
java.io.ByteArrayOutputStream updateStream = new java.io.ByteArrayOutputStream();
|
||||||
|
updateStream.write(CMD_W);
|
||||||
|
updateStream.write(0xC1);
|
||||||
|
// SBA to pos 0
|
||||||
|
updateStream.write(ORDER_SBA);
|
||||||
|
updateStream.write(0x40); updateStream.write(0x40);
|
||||||
|
// MF on pos 0: set foreground to green (0xF4)
|
||||||
|
updateStream.write(ORDER_MF);
|
||||||
|
updateStream.write(0x01); // 1 pair
|
||||||
|
updateStream.write(XA_FOREGROUND);
|
||||||
|
updateStream.write(0xF4);
|
||||||
|
// Data "123" following MF directly
|
||||||
|
updateStream.write(translator.stringToEbcdic("123"));
|
||||||
|
|
||||||
|
// MF on pos 10
|
||||||
|
updateStream.write(ORDER_SBA);
|
||||||
|
updateStream.write(addr10);
|
||||||
|
updateStream.write(ORDER_MF);
|
||||||
|
updateStream.write(0x01);
|
||||||
|
updateStream.write(XA_FOREGROUND);
|
||||||
|
updateStream.write(0xF5); // turquoise
|
||||||
|
updateStream.write(translator.stringToEbcdic("789"));
|
||||||
|
|
||||||
|
processor.processRecord(updateStream.toByteArray(), 0, updateStream.size(), false);
|
||||||
|
|
||||||
|
// Verify Field 1: pos 0 MUST still be a field attribute, NOT overwritten by '1'
|
||||||
|
assertTrue(screen.getCell(0).isFieldAttribute(), "Position 0 must remain a field attribute");
|
||||||
|
assertEquals((byte) 0xF4, screen.getCell(0).fg, "Position 0 field attribute must have updated fg");
|
||||||
|
assertEquals('1', screen.getCell(1).ucs4, "Data must start at pos 1, not overwrite pos 0");
|
||||||
|
assertEquals('2', screen.getCell(2).ucs4);
|
||||||
|
assertEquals('3', screen.getCell(3).ucs4);
|
||||||
|
|
||||||
|
// Verify Field 2: pos 10 MUST still be a field attribute, NOT overwritten by '7'
|
||||||
|
assertTrue(screen.getCell(10).isFieldAttribute(), "Position 10 must remain a field attribute");
|
||||||
|
assertEquals((byte) 0xF5, screen.getCell(10).fg, "Position 10 field attribute must have updated fg");
|
||||||
|
assertEquals('7', screen.getCell(11).ucs4, "Data must start at pos 11, not overwrite pos 10");
|
||||||
|
assertEquals('8', screen.getCell(12).ucs4);
|
||||||
|
assertEquals('9', screen.getCell(13).ucs4);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user