diff --git a/.gitignore b/.gitignore
index 87afada..fec019b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.idea/
/target/
+/connection.properties
diff --git a/pom.xml b/pom.xml
index b0e06c3..59b3bd1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -34,21 +34,33 @@
org.junit.jupiter
junit-jupiter-api
- 5.7.1
+ 5.8.2
test
org.junit.jupiter
junit-jupiter-engine
- 5.7.1
+ 5.8.2
test
+
+ org.jetbrains
+ annotations
+ 22.0.0
+
+ package
src
src/test/
+
+
+ resources
+
+
+
org.apache.maven.plugins
@@ -73,6 +85,11 @@
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 3.1.0
+
\ No newline at end of file
diff --git a/resources/connect_gui.fxml b/resources/connect_gui.fxml
new file mode 100644
index 0000000..b574c85
--- /dev/null
+++ b/resources/connect_gui.fxml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/fr/univ/lyon1/common/ServerConfiguration.java b/src/fr/univ/lyon1/common/ServerConfiguration.java
new file mode 100644
index 0000000..339cf0f
--- /dev/null
+++ b/src/fr/univ/lyon1/common/ServerConfiguration.java
@@ -0,0 +1,43 @@
+package fr.univ.lyon1.common;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+import java.util.Properties;
+
+public record ServerConfiguration(@NotNull String address, int port) {
+ @NotNull
+ private static final File file = new File("connection.properties");
+
+ public ServerConfiguration(@NotNull String address, int port) {
+ this.address = address;
+ this.port = port;
+ }
+
+ public static ServerConfiguration load() throws IOException, NumberFormatException {
+ // Check if file non exists, return error to launch server configuration
+ if (!file.exists()) {
+ System.out.println("File not exists");
+ throw new IOException("File not exists");
+ }
+
+ @NotNull final Properties properties = new Properties();
+ properties.load(new FileReader(file));
+ return new ServerConfiguration(properties.getProperty("address"), Integer.parseInt(properties.getProperty("port")));
+ }
+
+ public void save() throws IOException {
+ @NotNull final Properties properties = new Properties();
+ properties.setProperty("address", this.address);
+ properties.setProperty("port", String.valueOf(this.port));
+ properties.store(new FileWriter(file), "Information needed to connect to the server");
+ }
+
+ public @NotNull String getAddress() {
+ return address;
+ }
+
+ public int getPort() {
+ return port;
+ }
+}
diff --git a/src/fr/univ/lyon1/gui/ClientGUI.java b/src/fr/univ/lyon1/gui/ClientGUI.java
index 634aefc..9c870c0 100644
--- a/src/fr/univ/lyon1/gui/ClientGUI.java
+++ b/src/fr/univ/lyon1/gui/ClientGUI.java
@@ -3,15 +3,16 @@ package fr.univ.lyon1.gui;
import fr.univ.lyon1.client.Client;
import fr.univ.lyon1.client.ClientReceive;
import fr.univ.lyon1.common.Message;
+import fr.univ.lyon1.gui.handlers.MainHandler;
import java.io.IOException;
public class ClientGUI extends Client {
- private final MainGui gui;
+ private final MainHandler gui;
- public ClientGUI(MainGui gui, String address, int port) throws IOException, InterruptedException {
+ public ClientGUI(MainHandler handler, String address, int port) throws IOException, InterruptedException {
super(address, port);
- this.gui = gui;
+ this.gui = handler;
}
@Override
diff --git a/src/fr/univ/lyon1/gui/ClientPanel.java b/src/fr/univ/lyon1/gui/ClientPanel.java
index e47bd98..701de6b 100644
--- a/src/fr/univ/lyon1/gui/ClientPanel.java
+++ b/src/fr/univ/lyon1/gui/ClientPanel.java
@@ -1,5 +1,6 @@
package fr.univ.lyon1.gui;
+import fr.univ.lyon1.gui.handlers.MainHandler;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.scene.Parent;
@@ -12,15 +13,13 @@ import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
public class ClientPanel extends Parent {
- private TextArea textToSend = new TextArea();
- private ScrollPane scrollReceivedText = new ScrollPane();
- private TextFlow receivedText = new TextFlow();
- private Button sendBtn = new Button();
- private Button clearBtn = new Button();
- private final MainGui gui;
+ private final TextArea textToSend = new TextArea();
+ private final TextFlow receivedText = new TextFlow();
+ private final MainHandler gui;
- ClientPanel(MainGui gui) {
+ public ClientPanel(MainHandler gui) {
this.gui = gui;
+ ScrollPane scrollReceivedText = new ScrollPane();
scrollReceivedText.setLayoutX(20);
scrollReceivedText.setLayoutY(20);
scrollReceivedText.setPrefWidth(400);
@@ -29,13 +28,14 @@ public class ClientPanel extends Parent {
scrollReceivedText.vvalueProperty().bind(receivedText.heightProperty());
this.getChildren().add(scrollReceivedText);
+ Button sendBtn = new Button();
sendBtn.setPrefWidth(80);
int btnMargin = 20;
textToSend.setLayoutX(scrollReceivedText.getLayoutX());
textToSend.setLayoutY(scrollReceivedText.getLayoutY() + scrollReceivedText.getPrefHeight() + 20);
- textToSend.setPrefWidth(400-sendBtn.getPrefWidth()-btnMargin);
+ textToSend.setPrefWidth(400- sendBtn.getPrefWidth()-btnMargin);
textToSend.setPrefHeight(100);
this.getChildren().add(textToSend);
textToSend.setOnKeyPressed(this::textToSendKeyPressed);
@@ -49,6 +49,7 @@ public class ClientPanel extends Parent {
this.getChildren().add(sendBtn);
sendBtn.setOnAction(this::sendBtnAction);
+ Button clearBtn = new Button();
clearBtn.setText("Clear");
clearBtn.setLayoutX(sendBtn.getLayoutX());
clearBtn.setPrefWidth(sendBtn.getPrefWidth());
diff --git a/src/fr/univ/lyon1/gui/MainGui.java b/src/fr/univ/lyon1/gui/MainGui.java
index 2b1c691..4db7857 100644
--- a/src/fr/univ/lyon1/gui/MainGui.java
+++ b/src/fr/univ/lyon1/gui/MainGui.java
@@ -1,45 +1,35 @@
package fr.univ.lyon1.gui;
+import fr.univ.lyon1.gui.handlers.MainHandler;
+import fr.univ.lyon1.gui.handlers.ServerConfigurationHandler;
import javafx.application.Application;
-import javafx.scene.Group;
-import javafx.scene.Scene;
+import javafx.application.Platform;
import javafx.stage.Stage;
-import java.util.List;
+import java.io.IOException;
public class MainGui extends Application {
- private ClientPanel clientPanel;
- private ClientGUI client;
-
@Override
- public void start(Stage stage) throws Exception {
- List parameters = this.getParameters().getUnnamed();
- client = new ClientGUI(this, parameters.get(0), Integer.parseInt(parameters.get(1)));
- //ToDo: error management especially for bad server IP/port
- //ToDo: Server IP/port enter by user on the GUI
+ public void start(Stage stage) {
+ try {
+ new MainHandler().launch(stage);
+ } catch (IOException | InterruptedException e) {
+ // Launch server configuration
+ try {
+ new ServerConfigurationHandler().launch(stage);
+ } catch (IOException ex) {
+ // Can not launch server configuration, stop application
+ System.exit(1);
+ }
+ }
- stage.setTitle("Chat client");
- stage.setWidth(440);
-
- clientPanel = new ClientPanel(this);
- Group root = new Group();
- root.getChildren().add(clientPanel);
- Scene scene = new Scene(root, 600, 500);
-
- stage.setScene(scene);
- stage.show();
- client.run();
+ stage.setOnCloseRequest(event -> {
+ Platform.exit();
+ System.exit(0);
+ });
}
public static void main(String[] args) {
Application.launch(MainGui.class, args);
}
-
- public void sendMessage(String msg) {
- client.sendMessage(msg);
- }
-
- public void receiveMessage(String msg) {
- clientPanel.addMessage(msg);
- }
}
diff --git a/src/fr/univ/lyon1/gui/controller/ConnectGuiController.java b/src/fr/univ/lyon1/gui/controller/ConnectGuiController.java
new file mode 100644
index 0000000..153bdab
--- /dev/null
+++ b/src/fr/univ/lyon1/gui/controller/ConnectGuiController.java
@@ -0,0 +1,95 @@
+package fr.univ.lyon1.gui.controller;
+
+import fr.univ.lyon1.common.ServerConfiguration;
+import fr.univ.lyon1.gui.handlers.MainHandler;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.TextField;
+import javafx.stage.Stage;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.net.Socket;
+
+public class ConnectGuiController {
+ @FXML
+ public TextField addressTextField;
+ @FXML
+ public TextField portTextField;
+ @FXML
+ public Button connectButton;
+
+ /**
+ * Called by submit button in resources/connect_gui.fxml
+ */
+ public void connect() {
+ this.connectButton.setDisable(true);
+
+ @NotNull
+ final String address = this.addressTextField.getText();
+ @NotNull
+ final String port = this.portTextField.getText();
+
+ System.out.println("Checking if port is valid...");
+ // Check if the port is a number and if it's an available port
+ if (this.isNumber(port) && this.isAvailablePort(Integer.parseInt(port))) {
+ final int iPort = Integer.parseInt(port);
+ System.out.println("Port valid, next step...");
+ System.out.println("Checking if connection is available...");
+
+ // Check if the connection is available
+ if (this.isAccessible(address, iPort)) {
+ System.out.println("Connection available, saving file...");
+
+ try {
+ // Save server configuration
+ new ServerConfiguration(address, iPort).save();
+ Dialog.showSuccessDialog("Connecté", "Vous êtes bien connecté au serveur");
+ System.out.println("File saved, next step...");
+
+ @NotNull
+ final Stage stage = (Stage) this.connectButton.getScene().getWindow();
+ new MainHandler().launch(stage);
+ } catch (IOException e) {
+ Dialog.showErrorDialog("Erreur", "Impossible de sauvegarder les informations de connexion au serveur");
+ this.connectButton.setDisable(false);
+ System.out.println("Failed to save file, error: " + e.getMessage());
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } else {
+ System.out.println("Connection not available");
+ Dialog.showErrorDialog("Erreur de connexion", "Impossible de se connecter au serveur, veuillez vérifier les informations saisies");
+ this.connectButton.setDisable(false);
+ }
+ } else {
+ Dialog.showErrorDialog("Erreur", "Veuillez saisir un numéro de port valide");
+ this.connectButton.setDisable(false);
+ }
+ }
+
+ private boolean isNumber(String text) {
+ try {
+ Integer.parseInt(text);
+ return true;
+ } catch (NumberFormatException ignored) {
+ return false;
+ }
+ }
+
+ private boolean isAvailablePort(int port) {
+ return !(port < 0 || port > 0xFFFF);
+ }
+
+ private boolean isAccessible(String address, int port) {
+ try {
+ @NotNull
+ final Socket socket = new Socket(address, port);
+ socket.setSoTimeout(5 * 1000);
+
+ return true;
+ } catch (IOException exception) {
+ return false;
+ }
+ }
+}
diff --git a/src/fr/univ/lyon1/gui/controller/Dialog.java b/src/fr/univ/lyon1/gui/controller/Dialog.java
new file mode 100644
index 0000000..6623ab8
--- /dev/null
+++ b/src/fr/univ/lyon1/gui/controller/Dialog.java
@@ -0,0 +1,24 @@
+package fr.univ.lyon1.gui.controller;
+
+import javafx.scene.control.Alert;
+import org.jetbrains.annotations.NotNull;
+
+public class Dialog {
+ public static void showErrorDialog(String title, String description) {
+ @NotNull
+ final Alert alert = new Alert(Alert.AlertType.WARNING);
+ extracted(title, description, alert);
+ }
+
+ public static void showSuccessDialog(String title, String description) {
+ @NotNull
+ final Alert alert = new Alert(Alert.AlertType.INFORMATION);
+ extracted(title, description, alert);
+ }
+
+ private static void extracted(String title, String description, @NotNull Alert alert) {
+ alert.setTitle(title);
+ alert.setContentText(description);
+ alert.show();
+ }
+}
diff --git a/src/fr/univ/lyon1/gui/handlers/Handler.java b/src/fr/univ/lyon1/gui/handlers/Handler.java
new file mode 100644
index 0000000..a2fd0b5
--- /dev/null
+++ b/src/fr/univ/lyon1/gui/handlers/Handler.java
@@ -0,0 +1,9 @@
+package fr.univ.lyon1.gui.handlers;
+
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+public interface Handler {
+ void launch(Stage stage) throws IOException, InterruptedException;
+}
diff --git a/src/fr/univ/lyon1/gui/handlers/MainHandler.java b/src/fr/univ/lyon1/gui/handlers/MainHandler.java
new file mode 100644
index 0000000..761f65b
--- /dev/null
+++ b/src/fr/univ/lyon1/gui/handlers/MainHandler.java
@@ -0,0 +1,50 @@
+package fr.univ.lyon1.gui.handlers;
+
+import fr.univ.lyon1.common.ServerConfiguration;
+import fr.univ.lyon1.gui.ClientGUI;
+import fr.univ.lyon1.gui.ClientPanel;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+
+public class MainHandler implements Handler {
+ private ClientPanel clientPanel;
+ @Nullable
+ private ClientGUI client;
+
+ @Override
+ public void launch(Stage stage) throws IOException, InterruptedException {
+ @NotNull
+ final ServerConfiguration serverConfiguration = ServerConfiguration.load();
+ this.client = new ClientGUI(this, serverConfiguration.getAddress(), serverConfiguration.getPort());
+
+ stage.setTitle("Chat client");
+ stage.setWidth(440);
+
+ this.clientPanel = new ClientPanel(this);
+ Group root = new Group();
+ root.getChildren().add(this.clientPanel);
+ Scene scene = new Scene(root, 600, 500);
+
+ stage.setScene(scene);
+ stage.show();
+
+ if (this.client != null) {
+ this.client.run();
+ }
+ }
+
+ public void sendMessage(String msg) {
+ if (client != null) {
+ client.sendMessage(msg);
+ }
+ }
+
+ public void receiveMessage(String msg) {
+ clientPanel.addMessage(msg);
+ }
+}
diff --git a/src/fr/univ/lyon1/gui/handlers/ServerConfigurationHandler.java b/src/fr/univ/lyon1/gui/handlers/ServerConfigurationHandler.java
new file mode 100644
index 0000000..5c4211d
--- /dev/null
+++ b/src/fr/univ/lyon1/gui/handlers/ServerConfigurationHandler.java
@@ -0,0 +1,18 @@
+package fr.univ.lyon1.gui.handlers;
+
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+import java.io.IOException;
+
+public class ServerConfigurationHandler implements Handler {
+ @Override
+ public void launch(Stage stage) throws IOException {
+ FXMLLoader fxmlLoader = new FXMLLoader(ClassLoader.getSystemClassLoader().getResource("connect_gui.fxml"));
+ Scene scene = new Scene(fxmlLoader.load(), 320, 240);
+ stage.setTitle("Configuration du serveur");
+ stage.setScene(scene);
+ stage.show();
+ }
+}
diff --git a/src/module-info.java b/src/module-info.java
index 39f3612..0192096 100644
--- a/src/module-info.java
+++ b/src/module-info.java
@@ -1,9 +1,14 @@
-module fr.univ.lyon1.gui {
+module fr.univ.lyon {
requires javafx.controls;
requires javafx.fxml;
requires org.kordamp.bootstrapfx.core;
+ requires org.jetbrains.annotations;
opens fr.univ.lyon1.gui to javafx.fxml;
+
+ exports fr.univ.lyon1.common;
exports fr.univ.lyon1.gui;
+ exports fr.univ.lyon1.gui.controller;
+ exports fr.univ.lyon1.gui.handlers;
}
\ No newline at end of file