From 493b291dceb20b58b659764db2e4013681c26999 Mon Sep 17 00:00:00 2001 From: 2xsaiko Date: Wed, 22 Apr 2020 20:21:15 +0200 Subject: Improve connect dialog --- .../enigma/gui/dialog/ConnectToServerDialog.java | 163 ++++++++++++++------- .../java/cuchaz/enigma/gui/elements/MenuBar.java | 2 +- .../enigma/gui/elements/VerifiableTextField.java | 61 ++++++++ .../java/cuchaz/enigma/utils/ServerAddress.java | 78 ++++++++++ enigma/src/main/resources/lang/en_us.json | 10 +- .../test/java/cuchaz/enigma/ServerAddressTest.java | 29 ++++ 6 files changed, 288 insertions(+), 55 deletions(-) create mode 100644 enigma-swing/src/main/java/cuchaz/enigma/gui/elements/VerifiableTextField.java create mode 100644 enigma/src/main/java/cuchaz/enigma/utils/ServerAddress.java create mode 100644 enigma/src/test/java/cuchaz/enigma/ServerAddressTest.java diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java index c5f505cf..070d03d4 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java @@ -1,64 +1,131 @@ package cuchaz.enigma.gui.dialog; +import java.awt.*; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.swing.*; + +import cuchaz.enigma.gui.elements.VerifiableTextField; import cuchaz.enigma.network.EnigmaServer; import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.utils.ServerAddress; -import javax.swing.*; -import java.awt.Frame; - -public class ConnectToServerDialog { - - public static Result show(Frame parentComponent) { - JTextField usernameField = new JTextField(System.getProperty("user.name"), 20); - JPanel usernameRow = new JPanel(); - usernameRow.add(new JLabel(I18n.translate("prompt.connect.username"))); - usernameRow.add(usernameField); - JTextField ipField = new JTextField(20); - JPanel ipRow = new JPanel(); - ipRow.add(new JLabel(I18n.translate("prompt.connect.ip"))); - ipRow.add(ipField); - JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); - JPanel portRow = new JPanel(); - portRow.add(new JLabel(I18n.translate("prompt.port"))); - portRow.add(portField); - JPasswordField passwordField = new JPasswordField(20); - JPanel passwordRow = new JPanel(); - passwordRow.add(new JLabel(I18n.translate("prompt.password"))); - passwordRow.add(passwordField); - - int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{usernameRow, ipRow, portRow, passwordRow}, I18n.translate("prompt.connect.title"), JOptionPane.OK_CANCEL_OPTION); - if (response != JOptionPane.OK_OPTION) { - return null; +public class ConnectToServerDialog extends JDialog { + + private final JTextField usernameField; + private final VerifiableTextField ipField; + private final JPasswordField passwordField; + private boolean success = false; + + public ConnectToServerDialog(Frame owner) { + super(owner, I18n.translate("prompt.connect.title"), true); + + Container contentPane = getContentPane(); + contentPane.setLayout(new BorderLayout()); + Container inputContainer = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + usernameField = new JTextField(System.getProperty("user.name")); + ipField = new VerifiableTextField(); + passwordField = new JPasswordField(); + + List labels = Stream.of("prompt.connect.username", "prompt.connect.address", "prompt.password") + .map(I18n::translate) + .map(JLabel::new) + .collect(Collectors.toList()); + List inputs = Arrays.asList(usernameField, ipField, passwordField); + + for (int i = 0; i < inputs.size(); i += 1) { + c.gridy = i; + c.insets = new Insets(4, 4, 4, 4); + + c.gridx = 0; + c.weightx = 0.0; + c.anchor = GridBagConstraints.LINE_END; + c.fill = GridBagConstraints.NONE; + inputContainer.add(labels.get(i), c); + + c.gridx = 1; + c.weightx = 1.0; + c.anchor = GridBagConstraints.LINE_START; + c.fill = GridBagConstraints.HORIZONTAL; + inputs.get(i).addActionListener(event -> confirm()); + inputContainer.add(inputs.get(i), c); } + contentPane.add(inputContainer, BorderLayout.CENTER); + Container buttonContainer = new JPanel(new GridBagLayout()); + c = new GridBagConstraints(); + c.weightx = 1.0; + c.insets = new Insets(4, 4, 4, 4); + c.anchor = GridBagConstraints.LINE_END; + JButton connectButton = new JButton(I18n.translate("prompt.connect.confirm")); + connectButton.addActionListener(event -> confirm()); + buttonContainer.add(connectButton, c); + c.weightx = 0.0; + c.anchor = GridBagConstraints.CENTER; + JButton abortButton = new JButton(I18n.translate("prompt.connect.cancel")); + abortButton.addActionListener(event -> cancel()); + buttonContainer.add(abortButton, c); + contentPane.add(buttonContainer, BorderLayout.SOUTH); + + setLocationRelativeTo(owner); + setSize(new Dimension(400, 185)); + } - String username = usernameField.getText(); - String ip = ipField.getText(); - int port; - try { - port = Integer.parseInt(portField.getText()); - } catch (NumberFormatException e) { - JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); - return null; + private void confirm() { + if (validateInputs()) { + success = true; + setVisible(false); } - if (port < 0 || port >= 65536) { - JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); - return null; + } + + private void cancel() { + success = false; + setVisible(false); + } + + public boolean validateInputs() { + boolean error = false; + ipField.setErrorState(false); + + if (ServerAddress.from(ipField.getText(), EnigmaServer.DEFAULT_PORT) == null) { + ipField.setErrorState(true); + error = true; } - char[] password = passwordField.getPassword(); - return new Result(username, ip, port, password); + return !error; + } + + public Result getResult() { + if (!success) return null; + return new Result( + usernameField.getText(), + Objects.requireNonNull(ServerAddress.from(ipField.getText(), EnigmaServer.DEFAULT_PORT)), + passwordField.getPassword() + ); + } + + public static Result show(Frame parent) { + ConnectToServerDialog d = new ConnectToServerDialog(parent); + + d.setVisible(true); + Result r = d.getResult(); + + d.dispose(); + return r; } public static class Result { private final String username; - private final String ip; - private final int port; + private final ServerAddress address; private final char[] password; - public Result(String username, String ip, int port, char[] password) { + public Result(String username, ServerAddress address, char[] password) { this.username = username; - this.ip = ip; - this.port = port; + this.address = address; this.password = password; } @@ -66,12 +133,8 @@ public class ConnectToServerDialog { return username; } - public String getIp() { - return ip; - } - - public int getPort() { - return port; + public ServerAddress getAddress() { + return address; } public char[] getPassword() { diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 3378d1a4..1481a9dc 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -227,7 +227,7 @@ public class MenuBar { } this.gui.getController().disconnectIfConnected(null); try { - this.gui.getController().createClient(result.getUsername(), result.getIp(), result.getPort(), result.getPassword()); + this.gui.getController().createClient(result.getUsername(), result.getAddress().address, result.getAddress().port, result.getPassword()); } catch (IOException e) { JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE); this.gui.getController().disconnectIfConnected(null); diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/VerifiableTextField.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/VerifiableTextField.java new file mode 100644 index 00000000..41a24840 --- /dev/null +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/elements/VerifiableTextField.java @@ -0,0 +1,61 @@ +package cuchaz.enigma.gui.elements; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; + +import javax.swing.JTextField; +import javax.swing.text.Document; + +public class VerifiableTextField extends JTextField { + + private boolean hasError; + + public VerifiableTextField() { + } + + public VerifiableTextField(String text) { + super(text); + } + + public VerifiableTextField(int columns) { + super(columns); + } + + public VerifiableTextField(String text, int columns) { + super(text, columns); + } + + public VerifiableTextField(Document doc, String text, int columns) { + super(doc, text, columns); + } + + { + addFocusListener(new FocusAdapter() { + @Override + public void focusGained(FocusEvent e) { + setErrorState(false); + } + }); + } + + public void setErrorState(boolean b) { + this.hasError = b; + repaint(); + } + + @Override + public void paint(Graphics g) { + super.paint(g); + if (hasError) { + g.setColor(Color.RED); + int x1 = getWidth() - 9; + int x2 = getWidth() - 2; + int y1 = 1; + int y2 = 8; + g.fillPolygon(new int[]{x1, x2, x2}, new int[]{y1, y1, y2}, 3); + } + } + +} diff --git a/enigma/src/main/java/cuchaz/enigma/utils/ServerAddress.java b/enigma/src/main/java/cuchaz/enigma/utils/ServerAddress.java new file mode 100644 index 00000000..021d9a1a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/ServerAddress.java @@ -0,0 +1,78 @@ +package cuchaz.enigma.utils; + +import java.util.Objects; + +import javax.annotation.Nullable; + +public class ServerAddress { + + public final String address; + public final int port; + + private ServerAddress(String address, int port) { + this.address = address; + this.port = port; + } + + @Nullable + public static ServerAddress of(String address, int port) { + if (port < 0 || port > 65535) return null; + if (address == null) return null; + if (address.equals("")) return null; + if (!address.matches("[a-zA-Z0-9.:-]+")) return null; + if (address.startsWith("-") || address.endsWith("-")) return null; + return new ServerAddress(address, port); + } + + @Nullable + public static ServerAddress from(String s, int defaultPort) { + String address; + int idx = s.indexOf(']'); + if (s.startsWith("[") && idx != -1) { + address = s.substring(1, idx); + s = s.substring(idx + 1); + } else if (s.chars().filter(c -> c == ':').count() == 1) { + idx = s.indexOf(':'); + address = s.substring(0, idx); + s = s.substring(idx); + } else { + address = s; + s = ""; + } + + int port; + if (s.isEmpty()) { + port = defaultPort; + } else if (s.startsWith(":")) { + s = s.substring(1); + try { + port = Integer.parseInt(s); + } catch (NumberFormatException e) { + return null; + } + } else { + return null; + } + return ServerAddress.of(address, port); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ServerAddress that = (ServerAddress) o; + return port == that.port && + Objects.equals(address, that.address); + } + + @Override + public int hashCode() { + return Objects.hash(address, port); + } + + @Override + public String toString() { + return String.format("ServerAddress { address: '%s', port: %d }", address, port); + } + +} diff --git a/enigma/src/main/resources/lang/en_us.json b/enigma/src/main/resources/lang/en_us.json index b8db4b06..65902401 100644 --- a/enigma/src/main/resources/lang/en_us.json +++ b/enigma/src/main/resources/lang/en_us.json @@ -137,12 +137,14 @@ "prompt.open": "Open", "prompt.cancel": "Cancel", "prompt.connect.title": "Connect to server", - "prompt.connect.username": "Username", - "prompt.connect.ip": "IP", - "prompt.port": "Port", + "prompt.connect.username": "Username:", + "prompt.connect.address": "Address:", + "prompt.connect.confirm": "Connect", + "prompt.connect.cancel": "Cancel", + "prompt.port": "Port:", "prompt.port.nan": "Port is not a number", "prompt.port.invalid": "Port is out of range, should be between 0-65535", - "prompt.password": "Password", + "prompt.password": "Password:", "prompt.password.too_long": "Password is too long, it must be at most 255 characters.", "prompt.create_server.title": "Create server", diff --git a/enigma/src/test/java/cuchaz/enigma/ServerAddressTest.java b/enigma/src/test/java/cuchaz/enigma/ServerAddressTest.java new file mode 100644 index 00000000..fdeb2736 --- /dev/null +++ b/enigma/src/test/java/cuchaz/enigma/ServerAddressTest.java @@ -0,0 +1,29 @@ +package cuchaz.enigma; + +import cuchaz.enigma.utils.ServerAddress; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ServerAddressTest { + + @Test + public void validAddresses() { + assertEquals(ServerAddress.of("127.0.0.1", 22), ServerAddress.from("127.0.0.1", 22)); + assertEquals(ServerAddress.of("::1", 80), ServerAddress.from("[::1]:80", 22)); + assertEquals(ServerAddress.of("dblsaiko.net", 22), ServerAddress.from("dblsaiko.net", 22)); + assertEquals(ServerAddress.of("f00f:efee::127.0.0.1", 724), ServerAddress.from("[f00f:efee::127.0.0.1]:724", 22)); + assertEquals(ServerAddress.of("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:70", 22), ServerAddress.from("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:70", 22)); + assertEquals(ServerAddress.of("::1", 22), ServerAddress.from("::1", 22)); + assertEquals(ServerAddress.of("0", 22), ServerAddress.from("0", 22)); + } + + @Test + public void invalidAddresses() { + assertNull(ServerAddress.from("127.0.0.1:-72", 22)); + assertNull(ServerAddress.from("127.0.0.1:100000000", 22)); + assertNull(ServerAddress.from("127.0.0.1:lmao", 22)); + } + +} -- cgit v1.2.3