Add source code

This commit is contained in:
2023-03-24 15:49:26 +00:00
parent 3615925f20
commit 4e3aad2565
19 changed files with 648 additions and 68 deletions

150
pom.xml
View File

@@ -1,43 +1,167 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>uk.ac.soton.comp1206</groupId>
<groupId>uk.mgrove.ac.soton.comp1206</groupId>
<artifactId>app</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>19</maven.compiler.source>
<maven.compiler.target>19</maven.compiler.target>
<javafx.version>21-ea+5</javafx.version>
</properties>
<profiles>
<profile>
<id>shade</id>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
<classifier>mac</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
<classifier>linux</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
<classifier>win</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
<classifier>mac</classifier>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
<classifier>linux</classifier>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<shadedArtifactAttached>true</shadedArtifactAttached>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>uk.mgrove.ac.soton.comp1206.Launcher</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>19.0.2.1</version>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-swing</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-media</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>com.neovisionaries</groupId>
<artifactId>nv-websocket-client</artifactId>
<version>2.14</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.5.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<version>3.11.0</version>
<configuration>
<release>11</release>
<source>19</source>
<target>19</target>
<release>19</release>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.6</version>
<version>0.0.8</version>
<configuration>
<mainClass>uk.mgrove.ac.soton.comp1206/uk.mgrove.ac.soton.comp1206.App</mainClass>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<!-- Main class -->
<mainClass>uk.mgrove.ac.soton.comp1206.Launcher</mainClass>
</manifest>
</archive>
<descriptorRefs>
<!-- Includes project's dependencies -->
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>true</appendAssemblyId>
</configuration>
<executions>
<execution>
<!-- Default configuration for running -->
<!-- Usage: mvn clean javafx:run -->
<id>default-cli</id>
<configuration>
<mainClass>uk.ac.soton.comp1206.App</mainClass>
</configuration>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

View File

@@ -1,4 +1,22 @@
module uk.ac.soton.comp1206 {
module uk.mgrove.ac.soton.comp1206 {
requires java.scripting;
requires javafx.controls;
exports uk.ac.soton.comp1206;
requires javafx.fxml;
requires javafx.media;
requires javafx.swing;
requires java.desktop;
requires javafx.base;
requires org.apache.logging.log4j;
requires nv.websocket.client;
opens uk.mgrove.ac.soton.comp1206.ui to javafx.fxml;
opens uk.mgrove.ac.soton.comp1206.utility to javafx.fxml;
exports uk.mgrove.ac.soton.comp1206;
exports uk.mgrove.ac.soton.comp1206.ui;
exports uk.mgrove.ac.soton.comp1206.network;
exports uk.mgrove.ac.soton.comp1206.utility;
exports uk.mgrove.ac.soton.comp1206.ui.game;
opens uk.mgrove.ac.soton.comp1206.ui.game to javafx.fxml;
exports uk.mgrove.ac.soton.comp1206.ui.chat;
opens uk.mgrove.ac.soton.comp1206.ui.chat to javafx.fxml;
}

View File

@@ -1,30 +1,185 @@
package uk.ac.soton.comp1206;
package uk.mgrove.ac.soton.comp1206;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.image.Image;
import javafx.stage.Stage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.mgrove.ac.soton.comp1206.network.Communicator;
import uk.mgrove.ac.soton.comp1206.ui.BaseWindow;
import uk.mgrove.ac.soton.comp1206.ui.chat.ChatWindow;
import uk.mgrove.ac.soton.comp1206.ui.game.GameWindow;
import uk.mgrove.ac.soton.comp1206.ui.LoginWindow;
import uk.mgrove.ac.soton.comp1206.utility.Utility;
import java.util.HashMap;
import java.util.Map;
/**
* JavaFX App
* Our Chat application main class. This will be responsible for co-ordinating the application and handling the GUI.
*/
public class App extends Application {
@Override
public void start(Stage stage) {
var javaVersion = SystemInfo.javaVersion();
var javafxVersion = SystemInfo.javafxVersion();
var label = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
var scene = new Scene(new StackPane(label), 640, 480);
stage.setScene(scene);
stage.show();
}
private static final Logger logger = LogManager.getLogger(App.class);
private Communicator communicator;
private Stage stage;
private final StringProperty username = new SimpleStringProperty("Guest");
private final BooleanProperty gameOpen = new SimpleBooleanProperty(false);
private final Map<BaseWindow, Stage> windowStageMap = new HashMap<>();
private ChatWindow chatWindow;
/**
* Launch the JavaFX application
*
* @param args arguments given when starting the client
*/
public static void main(String[] args) {
logger.info("Starting client");
launch();
}
}
/**
* Start the Java FX process - prepare and display the first window
*
* @param stage the stage to use
*/
@Override
public void start(Stage stage) {
this.stage = stage;
communicator = new Communicator("ws://ofb-labs.soton.ac.uk:9500");
stage.setTitle("ECS Instant Messenger (EIM)");
stage.getIcons().add(new Image(getClass().getResourceAsStream("/uos_logo_square.png")));
stage.setOnCloseRequest(ev -> {
shutdown();
});
openLogin();
}
/**
* Display the login window
*/
public void openLogin() {
logger.info("Opening login window");
var window = new LoginWindow(this);
stage.setScene(window.getScene());
stage.show();
stage.centerOnScreen();
}
/**
* Display the chat window
*/
public void openChat() {
logger.info("Opening chat window");
chatWindow = new ChatWindow(this, communicator);
stage.setScene(chatWindow.getScene());
stage.show();
stage.centerOnScreen();
Utility.playAudio("connected.mp3");
}
/**
* Display the game
*/
public void openGame() {
if (gameOpen.get()) return;
logger.info("Opening game window");
gameOpen.set(true);
Stage stage = new Stage();
stage.setTitle("Game - ECS Instant Messenger (EIM)");
stage.getIcons().add(new Image(getClass().getResourceAsStream("/uos_logo_square.png")));
var window = new GameWindow(this, communicator);
stage.setScene(window.getScene());
stage.show();
stage.centerOnScreen();
windowStageMap.put(window, stage);
hideChat();
stage.setOnCloseRequest((e) -> {
shutdown();
});
}
/**
* Set the value of the gameOpen variable, which is used to track if the game window is open
* @param value value to set
*/
public void setGameOpen(boolean value) {
gameOpen.set(value);
}
/**
* Return to chat window from other open window
* @param window current window, which should be closed
*/
public void returnToChat(BaseWindow window) {
if (windowStageMap.containsKey(window)) windowStageMap.get(window).close();
showChat();
}
/**
* Move chat window to the background
*/
private void hideChat() {
if (chatWindow != null) stage.hide();
}
/**
* Show chat window
*/
private void showChat() {
if (chatWindow != null) stage.show();
}
/**
* Shutdown the application
*/
public void shutdown() {
logger.info("Shutting down");
System.exit(0);
}
/**
* Set the username from the login window
*
* @param username new username for current user
*/
public void setUsername(String username) {
logger.info("Username set to: " + username);
this.username.set(username);
}
/**
* Get the currently logged-in username
*
* @return username of current user
*/
public String getUsername() {
return this.username.get();
}
/**
* Get the username property
* @return username property
*/
public StringProperty usernameProperty() {
return username;
}
}

View File

@@ -0,0 +1,21 @@
package uk.mgrove.ac.soton.comp1206;
/**
* Launcher class (which is used to launch the main App)
*
* This is used to allow creation of a shaded jar file,
* which cannot extend from the JavaFX Application
*
* You do not need to worry about this class
*/
public class Launcher {
/**
* Start the main application and pass the arguments along
* @param args commandline arguments
*/
public static void main(String[] args) {
App.main(args);
}
}

View File

@@ -1,13 +0,0 @@
package uk.ac.soton.comp1206;
public class SystemInfo {
public static String javaVersion() {
return System.getProperty("java.version");
}
public static String javafxVersion() {
return System.getProperty("javafx.version");
}
}

View File

@@ -0,0 +1,102 @@
package uk.mgrove.ac.soton.comp1206.network;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketFactory;
import javafx.application.Platform;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javafx.scene.control.Alert;
import uk.mgrove.ac.soton.comp1206.ui.chat.ChatWindow;
import uk.mgrove.ac.soton.comp1206.ui.chat.MessageListener;
import java.util.ArrayList;
import java.util.List;
/**
* Uses web sockets to talk to a web socket server and relays communication to the ChatWindow
*
* YOU DO NOT NEED TO WORRY ABOUT THIS CLASS! Leave it be :-)
*/
public class Communicator {
private static final Logger logger = LogManager.getLogger(Communicator.class);
private WebSocket ws = null;
private String server;
private ChatWindow window;
private final List<MessageListener> listeners = new ArrayList<>();
/**
* Create a new communicator to the given web socket server
*
* @param server server to connect to
*/
public Communicator(String server) {
this.server = server;
try {
var socketFactory = new WebSocketFactory();
//Connect to the server
ws = socketFactory.createSocket(server);
ws.connect();
logger.info("Connected to " + server);
//When a message is received, call the receive method
ws.addListener(new WebSocketAdapter() {
@Override
public void onTextMessage(WebSocket websocket, String message) throws Exception {
Communicator.this.receive(websocket, message);
}
});
} catch (Exception e){
logger.error("Socket error: " + e.getMessage());
e.printStackTrace();
Alert error = new Alert(Alert.AlertType.ERROR,"Unable to communicate with the ECSChat server\n\n" + e.getMessage() + "\n\nPlease ensure you are connected to the VPN");
error.showAndWait();
System.exit(1);
}
}
/** Send a message to the server
*
* @param message Message to send
*/
public void send(String message) {
logger.info("Sending message: " + message);
ws.sendText(message);
}
/** Receive a message from the server. Relay to the Chat Window to handle
*
* @param websocket the socket
* @param message the message that was received
*/
private void receive(WebSocket websocket, String message) {
logger.info("Received: " + message);
for (MessageListener listener : listeners) {
Platform.runLater(() -> listener.receiveMessage(message));
}
}
/**
* Add listener for when messages are received
* @param listener listener to add
*/
public void addListener(MessageListener listener) {
listeners.add(listener);
}
/**
* Set the ChatWindow so the communicator can message it appropriately
* @param window chat window
*/
public void setWindow(ChatWindow window) {
this.window = window;
}
}

View File

@@ -20,6 +20,7 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.mgrove.ac.soton.comp1206.App;
import uk.mgrove.ac.soton.comp1206.network.Communicator;
import uk.mgrove.ac.soton.comp1206.ui.chat.ChatWindow;
import java.util.Arrays;

View File

@@ -0,0 +1,121 @@
package uk.mgrove.ac.soton.comp1206.ui;
import javafx.animation.FadeTransition;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.util.Duration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.mgrove.ac.soton.comp1206.App;
import java.net.URL;
import java.util.ResourceBundle;
/**
* Displays the Login Window, to collect the username and then start the chat
*/
public class LoginWindow implements Initializable {
private static final Logger logger = LogManager.getLogger(LoginWindow.class);
private final App app;
Scene scene = null;
Parent root = null;
@FXML
private TextField myUsernameInput;
/**
* Create a new Login Window, linked to the main app. This should get the username of the user.
* @param app the main app
*/
public LoginWindow(App app) {
this.app = app;
//Load the Login Window GUI
try {
//Instead of building this GUI programmatically, we are going to use FXML
var loader = new FXMLLoader(getClass().getResource("/login.fxml"));
//Link the GUI in the FXML to this class
loader.setController(this);
root = loader.load();
} catch (Exception e) {
//Handle any exceptions with loading the FXML
logger.error("Unable to read file: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
root.setOpacity(0);
fadeInWindow();
//We are the login window
scene = new Scene(root);
}
/**
* Get the scene contained inside the Login Window
* @return login window scene
*/
public Scene getScene() {
return scene;
}
/**
* Handle what happens when the user presses the login button
* @param event button clicked
*/
@FXML protected void handleLogin(ActionEvent event) {
String user = myUsernameInput.getText();
if(user.isBlank()) return;
app.setUsername(user);
app.openChat();
}
/**
* Handle what happens when the user presses enter on the username field
* @param event key pressed
*/
@FXML protected void handleUsernameKeypress(KeyEvent event) {
if(event.getCode() != KeyCode.ENTER) return;
handleLogin(null);
}
// /**
// * Initialise the Login Window
// * @param url
// * @param bundle
// */
// @Override
// public void initialize(URL url, ResourceBundle bundle) {
// //TODO: Any setting up of the window when it is initialised
// }
/**
* Fade the root node from invisible to visible
*/
public void fadeInWindow() {
FadeTransition ft = new FadeTransition(Duration.millis(500),root);
ft.setFromValue(0);
ft.setToValue(1);
ft.play();
}
/**
* Initialise window - none required
* @param url URL to use
* @param resourceBundle resource bundle to use
*/
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
// no initialisation required
}
}

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.chat;
import javafx.animation.FadeTransition;
import javafx.application.Platform;
@@ -17,6 +17,9 @@ import javafx.scene.text.Text;
import javafx.util.Duration;
import uk.mgrove.ac.soton.comp1206.App;
import uk.mgrove.ac.soton.comp1206.network.Communicator;
import uk.mgrove.ac.soton.comp1206.ui.BaseWindow;
import uk.mgrove.ac.soton.comp1206.ui.chat.Message;
import uk.mgrove.ac.soton.comp1206.ui.chat.UserList;
import uk.mgrove.ac.soton.comp1206.utility.Utility;
import java.awt.event.ActionEvent;

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.chat;
import javafx.scene.control.Hyperlink;
import javafx.scene.text.Text;

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.chat;
/**
* Interface for listeners that handle new messages being received by the Communicator class

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.chat;
import javafx.animation.FadeTransition;
import javafx.beans.binding.Bindings;

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.chat;
import javafx.animation.FadeTransition;
import javafx.beans.property.IntegerProperty;
@@ -6,8 +6,7 @@ import javafx.beans.property.StringProperty;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import javafx.util.Duration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.mgrove.ac.soton.comp1206.ui.chat.User;
import java.util.ArrayList;
import java.util.Comparator;

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.game;
/**
* Interface for listeners that handle a game block being clicked

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.game;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.game;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;

View File

@@ -1,31 +1,20 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.game;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.SplitPane;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import uk.mgrove.ac.soton.comp1206.App;
import uk.mgrove.ac.soton.comp1206.network.Communicator;
import uk.mgrove.ac.soton.comp1206.utility.Utility;
import uk.mgrove.ac.soton.comp1206.ui.BaseWindow;
import java.util.*;

View File

@@ -1,4 +1,4 @@
package uk.mgrove.ac.soton.comp1206.ui;
package uk.mgrove.ac.soton.comp1206.ui.game;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

View File

@@ -0,0 +1,60 @@
package uk.mgrove.ac.soton.comp1206.utility;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.regex.Pattern;
/**
* A utility class for quick and handy static functions
*/
public class Utility {
private static final Logger logger = LogManager.getLogger(Utility.class);
private static BooleanProperty audioEnabled = new SimpleBooleanProperty(true);
private static MediaPlayer mediaPlayer;
private static final String urlRegex = "((https?|file):((//)|(\\\\))+[\\w:#@%/;$()~_?+-=\\\\.&]*)";
private static final Pattern urlPattern = Pattern.compile(urlRegex, Pattern.CASE_INSENSITIVE);
/**
* Play an audio file
* @param file filepath of file to play
*/
public static void playAudio(String file) {
if (!audioEnabled.get()) return;
String toPlay = Utility.class.getResource("/" + file).toExternalForm();
logger.info("Playing audio: " + toPlay);
try {
Media play = new Media(toPlay);
mediaPlayer = new MediaPlayer(play);
mediaPlayer.play();
} catch (Exception e) {
audioEnabled.set(false);
e.printStackTrace();
logger.error("Unable to play audio file, disabling audio");
}
}
/**
* Get property for whether audio is enabled
* @return property
*/
public static BooleanProperty audioEnabledProperty() {
return audioEnabled;
}
/**
* Get regex pattern for URLs
* @return URL pattern
*/
public static Pattern getUrlPattern() {
return urlPattern;
}
}