summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar 2xsaiko2020-04-22 20:21:15 +0200
committerGravatar 2xsaiko2020-06-03 23:55:56 +0200
commit493b291dceb20b58b659764db2e4013681c26999 (patch)
treebabe0cb7d820d70bf774689be5323cb191ee34e9
parentFix 'Save Mappings' entry missing from the menu and ctrl+s not working (diff)
downloadenigma-493b291dceb20b58b659764db2e4013681c26999.tar.gz
enigma-493b291dceb20b58b659764db2e4013681c26999.tar.xz
enigma-493b291dceb20b58b659764db2e4013681c26999.zip
Improve connect dialog
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java163
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java2
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/elements/VerifiableTextField.java61
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/ServerAddress.java78
-rw-r--r--enigma/src/main/resources/lang/en_us.json10
-rw-r--r--enigma/src/test/java/cuchaz/enigma/ServerAddressTest.java29
6 files changed, 288 insertions, 55 deletions
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 @@
1package cuchaz.enigma.gui.dialog; 1package cuchaz.enigma.gui.dialog;
2 2
3import java.awt.*;
4import java.util.Arrays;
5import java.util.List;
6import java.util.Objects;
7import java.util.stream.Collectors;
8import java.util.stream.Stream;
9
10import javax.swing.*;
11
12import cuchaz.enigma.gui.elements.VerifiableTextField;
3import cuchaz.enigma.network.EnigmaServer; 13import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.utils.I18n; 14import cuchaz.enigma.utils.I18n;
15import cuchaz.enigma.utils.ServerAddress;
5 16
6import javax.swing.*; 17public class ConnectToServerDialog extends JDialog {
7import java.awt.Frame; 18
8 19 private final JTextField usernameField;
9public class ConnectToServerDialog { 20 private final VerifiableTextField ipField;
10 21 private final JPasswordField passwordField;
11 public static Result show(Frame parentComponent) { 22 private boolean success = false;
12 JTextField usernameField = new JTextField(System.getProperty("user.name"), 20); 23
13 JPanel usernameRow = new JPanel(); 24 public ConnectToServerDialog(Frame owner) {
14 usernameRow.add(new JLabel(I18n.translate("prompt.connect.username"))); 25 super(owner, I18n.translate("prompt.connect.title"), true);
15 usernameRow.add(usernameField); 26
16 JTextField ipField = new JTextField(20); 27 Container contentPane = getContentPane();
17 JPanel ipRow = new JPanel(); 28 contentPane.setLayout(new BorderLayout());
18 ipRow.add(new JLabel(I18n.translate("prompt.connect.ip"))); 29 Container inputContainer = new JPanel(new GridBagLayout());
19 ipRow.add(ipField); 30 GridBagConstraints c = new GridBagConstraints();
20 JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10); 31 usernameField = new JTextField(System.getProperty("user.name"));
21 JPanel portRow = new JPanel(); 32 ipField = new VerifiableTextField();
22 portRow.add(new JLabel(I18n.translate("prompt.port"))); 33 passwordField = new JPasswordField();
23 portRow.add(portField); 34
24 JPasswordField passwordField = new JPasswordField(20); 35 List<JLabel> labels = Stream.of("prompt.connect.username", "prompt.connect.address", "prompt.password")
25 JPanel passwordRow = new JPanel(); 36 .map(I18n::translate)
26 passwordRow.add(new JLabel(I18n.translate("prompt.password"))); 37 .map(JLabel::new)
27 passwordRow.add(passwordField); 38 .collect(Collectors.toList());
28 39 List<JTextField> inputs = Arrays.asList(usernameField, ipField, passwordField);
29 int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{usernameRow, ipRow, portRow, passwordRow}, I18n.translate("prompt.connect.title"), JOptionPane.OK_CANCEL_OPTION); 40
30 if (response != JOptionPane.OK_OPTION) { 41 for (int i = 0; i < inputs.size(); i += 1) {
31 return null; 42 c.gridy = i;
43 c.insets = new Insets(4, 4, 4, 4);
44
45 c.gridx = 0;
46 c.weightx = 0.0;
47 c.anchor = GridBagConstraints.LINE_END;
48 c.fill = GridBagConstraints.NONE;
49 inputContainer.add(labels.get(i), c);
50
51 c.gridx = 1;
52 c.weightx = 1.0;
53 c.anchor = GridBagConstraints.LINE_START;
54 c.fill = GridBagConstraints.HORIZONTAL;
55 inputs.get(i).addActionListener(event -> confirm());
56 inputContainer.add(inputs.get(i), c);
32 } 57 }
58 contentPane.add(inputContainer, BorderLayout.CENTER);
59 Container buttonContainer = new JPanel(new GridBagLayout());
60 c = new GridBagConstraints();
61 c.weightx = 1.0;
62 c.insets = new Insets(4, 4, 4, 4);
63 c.anchor = GridBagConstraints.LINE_END;
64 JButton connectButton = new JButton(I18n.translate("prompt.connect.confirm"));
65 connectButton.addActionListener(event -> confirm());
66 buttonContainer.add(connectButton, c);
67 c.weightx = 0.0;
68 c.anchor = GridBagConstraints.CENTER;
69 JButton abortButton = new JButton(I18n.translate("prompt.connect.cancel"));
70 abortButton.addActionListener(event -> cancel());
71 buttonContainer.add(abortButton, c);
72 contentPane.add(buttonContainer, BorderLayout.SOUTH);
73
74 setLocationRelativeTo(owner);
75 setSize(new Dimension(400, 185));
76 }
33 77
34 String username = usernameField.getText(); 78 private void confirm() {
35 String ip = ipField.getText(); 79 if (validateInputs()) {
36 int port; 80 success = true;
37 try { 81 setVisible(false);
38 port = Integer.parseInt(portField.getText());
39 } catch (NumberFormatException e) {
40 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE);
41 return null;
42 } 82 }
43 if (port < 0 || port >= 65536) { 83 }
44 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE); 84
45 return null; 85 private void cancel() {
86 success = false;
87 setVisible(false);
88 }
89
90 public boolean validateInputs() {
91 boolean error = false;
92 ipField.setErrorState(false);
93
94 if (ServerAddress.from(ipField.getText(), EnigmaServer.DEFAULT_PORT) == null) {
95 ipField.setErrorState(true);
96 error = true;
46 } 97 }
47 char[] password = passwordField.getPassword();
48 98
49 return new Result(username, ip, port, password); 99 return !error;
100 }
101
102 public Result getResult() {
103 if (!success) return null;
104 return new Result(
105 usernameField.getText(),
106 Objects.requireNonNull(ServerAddress.from(ipField.getText(), EnigmaServer.DEFAULT_PORT)),
107 passwordField.getPassword()
108 );
109 }
110
111 public static Result show(Frame parent) {
112 ConnectToServerDialog d = new ConnectToServerDialog(parent);
113
114 d.setVisible(true);
115 Result r = d.getResult();
116
117 d.dispose();
118 return r;
50 } 119 }
51 120
52 public static class Result { 121 public static class Result {
53 private final String username; 122 private final String username;
54 private final String ip; 123 private final ServerAddress address;
55 private final int port;
56 private final char[] password; 124 private final char[] password;
57 125
58 public Result(String username, String ip, int port, char[] password) { 126 public Result(String username, ServerAddress address, char[] password) {
59 this.username = username; 127 this.username = username;
60 this.ip = ip; 128 this.address = address;
61 this.port = port;
62 this.password = password; 129 this.password = password;
63 } 130 }
64 131
@@ -66,12 +133,8 @@ public class ConnectToServerDialog {
66 return username; 133 return username;
67 } 134 }
68 135
69 public String getIp() { 136 public ServerAddress getAddress() {
70 return ip; 137 return address;
71 }
72
73 public int getPort() {
74 return port;
75 } 138 }
76 139
77 public char[] getPassword() { 140 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 {
227 } 227 }
228 this.gui.getController().disconnectIfConnected(null); 228 this.gui.getController().disconnectIfConnected(null);
229 try { 229 try {
230 this.gui.getController().createClient(result.getUsername(), result.getIp(), result.getPort(), result.getPassword()); 230 this.gui.getController().createClient(result.getUsername(), result.getAddress().address, result.getAddress().port, result.getPassword());
231 } catch (IOException e) { 231 } catch (IOException e) {
232 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE); 232 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE);
233 this.gui.getController().disconnectIfConnected(null); 233 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 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.Color;
4import java.awt.Graphics;
5import java.awt.event.FocusAdapter;
6import java.awt.event.FocusEvent;
7
8import javax.swing.JTextField;
9import javax.swing.text.Document;
10
11public class VerifiableTextField extends JTextField {
12
13 private boolean hasError;
14
15 public VerifiableTextField() {
16 }
17
18 public VerifiableTextField(String text) {
19 super(text);
20 }
21
22 public VerifiableTextField(int columns) {
23 super(columns);
24 }
25
26 public VerifiableTextField(String text, int columns) {
27 super(text, columns);
28 }
29
30 public VerifiableTextField(Document doc, String text, int columns) {
31 super(doc, text, columns);
32 }
33
34 {
35 addFocusListener(new FocusAdapter() {
36 @Override
37 public void focusGained(FocusEvent e) {
38 setErrorState(false);
39 }
40 });
41 }
42
43 public void setErrorState(boolean b) {
44 this.hasError = b;
45 repaint();
46 }
47
48 @Override
49 public void paint(Graphics g) {
50 super.paint(g);
51 if (hasError) {
52 g.setColor(Color.RED);
53 int x1 = getWidth() - 9;
54 int x2 = getWidth() - 2;
55 int y1 = 1;
56 int y2 = 8;
57 g.fillPolygon(new int[]{x1, x2, x2}, new int[]{y1, y1, y2}, 3);
58 }
59 }
60
61}
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 @@
1package cuchaz.enigma.utils;
2
3import java.util.Objects;
4
5import javax.annotation.Nullable;
6
7public class ServerAddress {
8
9 public final String address;
10 public final int port;
11
12 private ServerAddress(String address, int port) {
13 this.address = address;
14 this.port = port;
15 }
16
17 @Nullable
18 public static ServerAddress of(String address, int port) {
19 if (port < 0 || port > 65535) return null;
20 if (address == null) return null;
21 if (address.equals("")) return null;
22 if (!address.matches("[a-zA-Z0-9.:-]+")) return null;
23 if (address.startsWith("-") || address.endsWith("-")) return null;
24 return new ServerAddress(address, port);
25 }
26
27 @Nullable
28 public static ServerAddress from(String s, int defaultPort) {
29 String address;
30 int idx = s.indexOf(']');
31 if (s.startsWith("[") && idx != -1) {
32 address = s.substring(1, idx);
33 s = s.substring(idx + 1);
34 } else if (s.chars().filter(c -> c == ':').count() == 1) {
35 idx = s.indexOf(':');
36 address = s.substring(0, idx);
37 s = s.substring(idx);
38 } else {
39 address = s;
40 s = "";
41 }
42
43 int port;
44 if (s.isEmpty()) {
45 port = defaultPort;
46 } else if (s.startsWith(":")) {
47 s = s.substring(1);
48 try {
49 port = Integer.parseInt(s);
50 } catch (NumberFormatException e) {
51 return null;
52 }
53 } else {
54 return null;
55 }
56 return ServerAddress.of(address, port);
57 }
58
59 @Override
60 public boolean equals(Object o) {
61 if (this == o) return true;
62 if (o == null || getClass() != o.getClass()) return false;
63 ServerAddress that = (ServerAddress) o;
64 return port == that.port &&
65 Objects.equals(address, that.address);
66 }
67
68 @Override
69 public int hashCode() {
70 return Objects.hash(address, port);
71 }
72
73 @Override
74 public String toString() {
75 return String.format("ServerAddress { address: '%s', port: %d }", address, port);
76 }
77
78}
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 @@
137 "prompt.open": "Open", 137 "prompt.open": "Open",
138 "prompt.cancel": "Cancel", 138 "prompt.cancel": "Cancel",
139 "prompt.connect.title": "Connect to server", 139 "prompt.connect.title": "Connect to server",
140 "prompt.connect.username": "Username", 140 "prompt.connect.username": "Username:",
141 "prompt.connect.ip": "IP", 141 "prompt.connect.address": "Address:",
142 "prompt.port": "Port", 142 "prompt.connect.confirm": "Connect",
143 "prompt.connect.cancel": "Cancel",
144 "prompt.port": "Port:",
143 "prompt.port.nan": "Port is not a number", 145 "prompt.port.nan": "Port is not a number",
144 "prompt.port.invalid": "Port is out of range, should be between 0-65535", 146 "prompt.port.invalid": "Port is out of range, should be between 0-65535",
145 "prompt.password": "Password", 147 "prompt.password": "Password:",
146 "prompt.password.too_long": "Password is too long, it must be at most 255 characters.", 148 "prompt.password.too_long": "Password is too long, it must be at most 255 characters.",
147 "prompt.create_server.title": "Create server", 149 "prompt.create_server.title": "Create server",
148 150
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 @@
1package cuchaz.enigma;
2
3import cuchaz.enigma.utils.ServerAddress;
4import org.junit.Test;
5
6import static org.junit.Assert.assertEquals;
7import static org.junit.Assert.assertNull;
8
9public class ServerAddressTest {
10
11 @Test
12 public void validAddresses() {
13 assertEquals(ServerAddress.of("127.0.0.1", 22), ServerAddress.from("127.0.0.1", 22));
14 assertEquals(ServerAddress.of("::1", 80), ServerAddress.from("[::1]:80", 22));
15 assertEquals(ServerAddress.of("dblsaiko.net", 22), ServerAddress.from("dblsaiko.net", 22));
16 assertEquals(ServerAddress.of("f00f:efee::127.0.0.1", 724), ServerAddress.from("[f00f:efee::127.0.0.1]:724", 22));
17 assertEquals(ServerAddress.of("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:70", 22), ServerAddress.from("aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:aaaa:70", 22));
18 assertEquals(ServerAddress.of("::1", 22), ServerAddress.from("::1", 22));
19 assertEquals(ServerAddress.of("0", 22), ServerAddress.from("0", 22));
20 }
21
22 @Test
23 public void invalidAddresses() {
24 assertNull(ServerAddress.from("127.0.0.1:-72", 22));
25 assertNull(ServerAddress.from("127.0.0.1:100000000", 22));
26 assertNull(ServerAddress.from("127.0.0.1:lmao", 22));
27 }
28
29}