diff --git a/client/pom.xml b/client/pom.xml index 0ec1214..1117e56 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -5,7 +5,7 @@ ace-of-shades nl.andrewlalis - 3.1 + 4.0 4.0.0 diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/Launcher.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/Launcher.java index 3fd3c47..7de48db 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/launcher/Launcher.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/Launcher.java @@ -1,9 +1,7 @@ package nl.andrewlalis.aos_client.launcher; import nl.andrewlalis.aos_client.Client; -import nl.andrewlalis.aos_client.launcher.servers.ServerInfo; -import nl.andrewlalis.aos_client.launcher.servers.ServerInfoCellRenderer; -import nl.andrewlalis.aos_client.launcher.servers.ServerInfoListModel; +import nl.andrewlalis.aos_client.launcher.servers.*; import javax.swing.*; import java.awt.*; @@ -12,6 +10,7 @@ import java.awt.event.MouseEvent; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -22,7 +21,9 @@ import java.util.regex.Pattern; * the menu that the user interacts with before joining a game. */ public class Launcher extends JFrame { - private static final Pattern addressPattern = Pattern.compile("(.+):(\\d+)"); + public static final Pattern addressPattern = Pattern.compile("(.+):(\\d+)"); + public static final Path DATA_DIR = Path.of(System.getProperty("user.home"), "ace-of-shades"); + public static final Pattern usernamePattern = Pattern.compile("[a-zA-Z0-9_-]+"); public Launcher() throws HeadlessException { super("Ace of Shades - Launcher"); @@ -36,9 +37,6 @@ public class Launcher extends JFrame { private Container buildContent() { JTabbedPane mainPanel = new JTabbedPane(SwingConstants.TOP, JTabbedPane.SCROLL_TAB_LAYOUT); mainPanel.addTab("Servers", null, this.getServersPanel(), "View a list of available servers."); -// -// JPanel settingsPanel = new JPanel(); -// mainPanel.addTab("Settings", null, settingsPanel, "Change game settings."); return mainPanel; } @@ -47,8 +45,6 @@ public class Launcher extends JFrame { JPanel panel = new JPanel(new BorderLayout()); ServerInfoListModel listModel = new ServerInfoListModel(); - listModel.add(new ServerInfo("one", "localhost:8035", "andrew")); - listModel.add(new ServerInfo("two", "localhost:25565", null)); JList serversList = new JList<>(listModel); serversList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); serversList.setCellRenderer(new ServerInfoCellRenderer()); @@ -68,8 +64,15 @@ public class Launcher extends JFrame { menu.add(connectItem); JMenuItem editItem = new JMenuItem("Edit"); editItem.addActionListener(a -> { - // TODO: Open edit dialog. - listModel.serverEdited(); + EditServerDialog dialog = new EditServerDialog((Frame) SwingUtilities.getWindowAncestor(panel), server); + dialog.setVisible(true); + ServerInfo editedInfo = dialog.getServerInfo(); + if (editedInfo != null) { + server.setName(editedInfo.getName()); + server.setHost(editedInfo.getHost()); + server.setUsername(editedInfo.getUsername()); + listModel.serverEdited(); + } }); menu.add(editItem); JMenuItem removeItem = new JMenuItem("Remove"); @@ -94,7 +97,12 @@ public class Launcher extends JFrame { JButton addServerButton = new JButton("Add Server"); addServerButton.setToolTipText("Add a new server to the list."); addServerButton.addActionListener(e -> { - // TODO: Add server dialog. + AddServerDialog dialog = new AddServerDialog(this); + dialog.setVisible(true); + ServerInfo server = dialog.getServerInfo(); + if (server != null) { + listModel.add(server); + } }); buttonPanel.add(addServerButton); JButton directConnectButton = new JButton("Direct Connect"); diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/AddServerDialog.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/AddServerDialog.java new file mode 100644 index 0000000..59788eb --- /dev/null +++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/AddServerDialog.java @@ -0,0 +1,113 @@ +package nl.andrewlalis.aos_client.launcher.servers; + +import nl.andrewlalis.aos_client.launcher.Launcher; + +import javax.swing.*; +import java.awt.*; +import java.util.ArrayList; +import java.util.List; + +public class AddServerDialog extends JDialog { + protected JTextField serverNameField; + protected JTextField serverAddressField; + protected JTextField usernameField; + + private ServerInfo serverInfo; + + public AddServerDialog(Frame owner) { + super(owner, true); + this.setTitle("Add Server"); + this.setContentPane(this.getContent()); + this.pack(); + this.setLocationRelativeTo(owner); + } + + public ServerInfo getServerInfo() { + return this.serverInfo; + } + + private Container getContent() { + JPanel container = new JPanel(new BorderLayout()); + JPanel inputPanel = new JPanel(new GridBagLayout()); + GridBagConstraints c = new GridBagConstraints(); + c.insets = new Insets(5, 5, 5, 5); + + c.gridx = 0; + c.gridy = 0; + inputPanel.add(new JLabel("Name"), c); + serverNameField = new JTextField(20); + c.gridx++; + inputPanel.add(serverNameField, c); + + c.gridx = 0; + c.gridy++; + inputPanel.add(new JLabel("Address"), c); + serverAddressField = new JTextField(20); + c.gridx++; + inputPanel.add(serverAddressField, c); + + c.gridy++; + c.gridx = 0; + inputPanel.add(new JLabel("Username"), c); + usernameField = new JTextField(20); + c.gridx++; + inputPanel.add(usernameField, c); + + container.add(inputPanel, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(e -> { + this.serverInfo = null; + this.dispose(); + }); + JButton okButton = new JButton("Ok"); + okButton.addActionListener(e -> { + var messages = this.validateInputs(); + if (!messages.isEmpty()) { + String msg = String.join("\n", messages); + JOptionPane.showMessageDialog(this, "The information you entered is not valid:\n" + msg, "Invalid Server Data", JOptionPane.WARNING_MESSAGE); + } else { + String username = this.usernameField.getText(); + if (username.isBlank()) { + username = null; + } + this.serverInfo = new ServerInfo(this.serverNameField.getText(), this.serverAddressField.getText(), username); + this.dispose(); + } + }); + buttonPanel.add(okButton); + buttonPanel.add(cancelButton); + + container.add(buttonPanel, BorderLayout.SOUTH); + + return container; + } + + private List validateInputs() { + String name = this.serverNameField.getText(); + String address = this.serverAddressField.getText(); + String username = this.usernameField.getText(); + List messages = new ArrayList<>(); + + if (name == null || name.isBlank()) { + messages.add("Server name cannot be blank."); + } + if (name != null && name.length() > 32) { + messages.add("Server name is too long."); + } + if (address == null || address.isBlank()) { + messages.add("Server address cannot be blank."); + } + if (address != null && !Launcher.addressPattern.matcher(address).matches()) { + messages.add("Server address is not properly formatted as HOST:PORT."); + } + if (username != null && !username.isBlank() && username.length() > 16) { + messages.add("Username is too long. Maximum of 16 characters."); + } + if (username != null && !Launcher.usernamePattern.matcher(username).matches()) { + messages.add("Username should contain only letters, numbers, underscores, and hyphens."); + } + return messages; + } +} diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/EditServerDialog.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/EditServerDialog.java new file mode 100644 index 0000000..0a2766d --- /dev/null +++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/EditServerDialog.java @@ -0,0 +1,13 @@ +package nl.andrewlalis.aos_client.launcher.servers; + +import java.awt.*; + +public class EditServerDialog extends AddServerDialog { + public EditServerDialog(Frame owner, ServerInfo server) { + super(owner); + this.setTitle("Edit Server - " + server.getName()); + this.serverNameField.setText(server.getName()); + this.serverAddressField.setText(server.getHost()); + this.usernameField.setText(server.getUsername()); + } +} diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfo.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfo.java index fdfbc8e..cb4fba7 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfo.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfo.java @@ -1,8 +1,9 @@ package nl.andrewlalis.aos_client.launcher.servers; +import java.io.Serializable; import java.util.Objects; -public class ServerInfo implements Comparable { +public class ServerInfo implements Comparable, Serializable { private String name; private String host; private String username; diff --git a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfoListModel.java b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfoListModel.java index 4bd4408..146d172 100644 --- a/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfoListModel.java +++ b/client/src/main/java/nl/andrewlalis/aos_client/launcher/servers/ServerInfoListModel.java @@ -1,15 +1,39 @@ package nl.andrewlalis.aos_client.launcher.servers; +import nl.andrewlalis.aos_client.launcher.Launcher; + import javax.swing.*; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +/** + * Model which represents the list of servers. This model is backed by a file + * containing the serialized list, which is updated any time a server is added, + * edited, or removed. + */ public class ServerInfoListModel extends AbstractListModel { + public static final Path SERVERS_FILE = Launcher.DATA_DIR.resolve("servers.dat"); + private final List servers; + @SuppressWarnings("unchecked") public ServerInfoListModel() { - this.servers = new ArrayList<>(); + List list = null; + if (Files.exists(SERVERS_FILE)) { + try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(SERVERS_FILE))) { + list = (ArrayList) ois.readObject(); + } catch (IOException | ClassNotFoundException e) { + e.printStackTrace(); + } + } + if (list == null) list = new ArrayList<>(); + servers = list; } public void add(ServerInfo server) { @@ -17,6 +41,7 @@ public class ServerInfoListModel extends AbstractListModel { this.servers.add(server); this.servers.sort(Comparator.naturalOrder()); this.fireContentsChanged(this, 0, this.getSize()); + this.save(); } public void remove(ServerInfo server) { @@ -24,10 +49,12 @@ public class ServerInfoListModel extends AbstractListModel { if (index == -1) return; this.servers.remove(index); this.fireIntervalRemoved(this, index, index); + this.save(); } public void serverEdited() { this.fireContentsChanged(this, 0, this.getSize()); + this.save(); } @Override @@ -39,4 +66,15 @@ public class ServerInfoListModel extends AbstractListModel { public ServerInfo getElementAt(int index) { return this.servers.get(index); } + + private void save() { + try { + Files.createDirectories(SERVERS_FILE.getParent()); + ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(SERVERS_FILE)); + oos.writeObject(this.servers); + oos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/core/pom.xml b/core/pom.xml index efc9979..4d86160 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ ace-of-shades nl.andrewlalis - 3.1 + 4.0 4.0.0 diff --git a/pom.xml b/pom.xml index 4cd0375..187fae8 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ nl.andrewlalis ace-of-shades pom - 3.1 + 4.0 server client diff --git a/server/pom.xml b/server/pom.xml index d557a68..d7adf69 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -5,7 +5,7 @@ ace-of-shades nl.andrewlalis - 3.1 + 4.0 4.0.0