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