feat: client/server working for adding lines, can undo locally, added a colorpicker and drawing in color is now a thing

This commit is contained in:
Emi Boucly 2025-03-24 16:43:16 +01:00
parent a8b2a0ced6
commit de645a8b1e
11 changed files with 939 additions and 40 deletions

View file

@ -47,6 +47,11 @@
<artifactId>validatorfx</artifactId>
<version>0.5.1</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
</dependencies>
<build>

View file

@ -1,5 +1,13 @@
package fr.emiko.graphicalapp;
import fr.emiko.graphicsElement.Line;
import fr.emiko.net.DrawClient;
import fr.emiko.net.DrawServer;
import fr.emiko.net.Event;
import fr.emiko.net.User;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
@ -7,26 +15,32 @@ import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.control.Label;
import javafx.scene.control.*;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Slider;
import javafx.scene.effect.BoxBlur;
import javafx.scene.input.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.paint.Color;
import fr.emiko.graphicsElement.Stroke;
import javafx.scene.robot.Robot;
import javafx.scene.transform.Scale;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.json.JSONObject;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.Vector;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HelloController implements Initializable {
private final Pattern hostPortPattern = Pattern.compile("^([-.a-zA-Z0-9]+)(?::([0-9]{1,5}))?$");
public Canvas drawingCanvas;
public MenuItem saveButton;
public MenuItem loadButton;
@ -36,13 +50,30 @@ public class HelloController implements Initializable {
public ScrollPane scrollPane;
public Label brushSizeLabel;
public Pane pane;
public MenuItem hostButton;
public MenuItem joinButton;
public MenuItem disconnectButton;
public SplitPane mainPane;
public MenuItem stopHostButton;
public ColorPicker colorPicker;
public ListView layerListView;
public Button addLayerButton;
public Button removeLayerButton;
private double posX = 0;
private double posY = 0;
private double mouseX = 0;
private double mouseY = 0;
private Vector<Stroke> strokes = new Vector<>();
private Vector<Vector<Stroke>> lastSaved = new Vector<>();
private Vector<Vector<Stroke>> lines = new Vector<>();
private Vector<Line> lastSaved = new Vector<>();
private Vector<Line> lines = new Vector<>();
private User user;
private boolean connected;
private DrawClient client;
private ToggleButton hostButtonToggle = new ToggleButton();
private DrawServer server;
private Canvas currentLayer;
private ObservableList<Canvas> layerObservableList = FXCollections.observableArrayList();
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
@ -56,6 +87,111 @@ public class HelloController implements Initializable {
scrollPane.prefViewportHeightProperty().bind(pane.layoutYProperty());
scrollPane.prefViewportWidthProperty().bind(pane.layoutXProperty());
stopHostButton.setOnAction(this::onActionStopHost);
hostButton.setOnAction(this::onActionHost);
joinButton.setOnAction(this::onActionJoin);
disconnectButton.setOnAction(this::onActionDisconnect);
stopHostButton.disableProperty().bind(hostButtonToggle.selectedProperty().not());
disconnectButton.disableProperty().bind(hostButtonToggle.selectedProperty().not());
hostButtonToggle.setSelected(false);
mainPane.disableProperty().bind(hostButtonToggle.selectedProperty().not());
layerListView.setItems(layerObservableList);
}
private void onActionStopHost(ActionEvent actionEvent) {
client.close();
if (this.server != null) {
try {
server.close();
} catch (IOException e) {
showErrorDialog(e, "Could not close server instance");
}
}
}
private void onActionDisconnect(ActionEvent actionEvent) {
client.close();
}
private void onActionJoin(ActionEvent actionEvent) {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("Join");
dialog.setHeaderText(null);
dialog.setContentText("Enter distant address");
Optional<String> result = dialog.showAndWait();
if (result.isPresent()) {
try {
Matcher matcher = hostPortPattern.matcher(result.get());
matcher.matches();
String host = matcher.group(1);
String port = matcher.group(2);
connectClient(host, port == null ? 8090 : Integer.parseInt(port));
} catch (NumberFormatException e) {
showErrorDialog(e, "Invalid distant address");
} catch (IOException e) {
showErrorDialog(e, "Could not connect to host");
}
}
}
private void onActionHost(ActionEvent actionEvent) {
TextInputDialog dialog = new TextInputDialog();
dialog.setTitle("Host");
dialog.setContentText("Which port do you want to use? (default: 8090)");
Optional<String> result = dialog.showAndWait();
if (result.isPresent()) {
try {
server = new DrawServer(result.get().isEmpty() ? 8090 : Integer.parseInt(result.get()));
Thread thread = new Thread(server::acceptClients);
thread.setDaemon(true);
thread.start();
connectClient("localhost", result.get().isEmpty() ? 8090 : Integer.parseInt(result.get()));
} catch (NumberFormatException | IOException e) {
showErrorDialog(e, "Invalid port number");
}
}
}
private void connectClient(String host, int port) throws IOException {
this.client = new DrawClient(host, port, this);
hostButtonToggle.setSelected(true);
client.sendAuthEvent(String.valueOf(new Random().nextInt()));
}
private void showErrorDialog(Exception ex, String context) {
Alert alert = new Alert(Alert.AlertType.ERROR);
alert.setTitle("An error occured!");
alert.setHeaderText(null);
alert.setContentText(context);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
String exceptionText = sw.toString();
Label label = new Label("The exception stacktrace was:");
TextArea textArea = new TextArea(exceptionText);
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);
GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);
GridPane expContent = new GridPane();
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(label, 0, 0);
expContent.add(textArea, 0, 1);
alert.getDialogPane().setExpandableContent(expContent);
alert.showAndWait();
}
private void setupCanvas() {
@ -73,6 +209,11 @@ public class HelloController implements Initializable {
onScrollZoom(event);
event.consume();
}});
BoxBlur blur = new BoxBlur();
blur.setHeight(1);
blur.setWidth(1);
blur.setIterations(1);
drawingCanvas.getGraphicsContext2D().setEffect(blur);
}
private void onActionKeyPressed(KeyEvent keyEvent) {
@ -86,7 +227,7 @@ public class HelloController implements Initializable {
gc.fillRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
for (Vector<Stroke> strokeVector : lines) {
for (Stroke stroke: strokeVector) {
stroke.draw(gc);
stroke.draw(gc, stroke.getColor());
//System.out.println(stroke);
}
}
@ -164,7 +305,7 @@ public class HelloController implements Initializable {
System.out.println(lastSaved.size());
for (Vector<Stroke> strokeVector : lastSaved) {
for (Stroke stroke: strokeVector) {
stroke.draw(gc);
stroke.draw(gc, colorPicker.getValue());
System.out.println(stroke);
}
}
@ -173,7 +314,7 @@ public class HelloController implements Initializable {
private void onActionSave(ActionEvent actionEvent) {
GraphicsContext gc = drawingCanvas.getGraphicsContext2D();
lastSaved = (Vector<Vector<Stroke>>) lines.clone();
lastSaved = (Vector<Line>) lines.clone();
System.out.println(lastSaved.size());
}
@ -182,10 +323,17 @@ public class HelloController implements Initializable {
posY = 0;
mouseX = 0;
mouseY = 0;
lines.add((Vector<Stroke>) strokes.clone());
Line line = new Line();
for (Stroke stroke: strokes) {
line.add(stroke);
}
lines.add((Line) line.clone());
System.out.println(lines.size());
System.out.println(lines);
System.out.println(new Event("ADDLINE", line.toJSONObject()));
strokes.clear();
client.sendEvent(new Event(Event.ADDLINE, line.toJSONObject()));
}
private void printLine(MouseEvent mouseEvent) {
@ -197,15 +345,74 @@ public class HelloController implements Initializable {
posY = mouseEvent.getY();
}
Stroke stroke = new Stroke(posX, posY, mouseEvent.getX(), mouseEvent.getY(), brushSizeSlider.getValue());
Stroke stroke = new Stroke(posX, posY, mouseEvent.getX(), mouseEvent.getY(), brushSizeSlider.getValue(), colorPicker.getValue());
strokes.add(stroke);
stroke.draw(gc);
stroke.draw(gc, colorPicker.getValue());
posX = mouseEvent.getX();
posY = mouseEvent.getY();
} else if (mouseEvent.isSecondaryButtonDown()) {
}
}
public void handleEvent(Event event) {
System.out.println("Received new event !:" + event.toJSON());
String type = event.getType();
switch (type) {
case Event.LINE -> {
doImportLine(event.getContent());
}
case Event.DELLINE -> {
doDeleteLine(event.getContent());
}
default -> {}
}
}
private void doDeleteLine(JSONObject content) {
lines.remove(Line.fromJSONArray(content.getJSONArray("line")));
GraphicsContext gc = drawingCanvas.getGraphicsContext2D();
lines.sort(new Comparator<Line>() {
@Override
public int compare(Line o1, Line o2) {
if (o1.getTimestamp() < o2.getTimestamp()) {
return 1;
} else {
return 0;
}
}
});
for (Line line: lines) {
for (Stroke stroke: line) {
stroke.draw(gc, colorPicker.getValue());
}
}
}
private void doImportLine(JSONObject content) {
Line importedLine = Line.fromJSONArray(content.getJSONArray("line"));
this.lines.add(importedLine);
GraphicsContext gc = drawingCanvas.getGraphicsContext2D();
gc.clearRect(0, 0, drawingCanvas.getWidth(), drawingCanvas.getHeight());
lines.sort(new Comparator<Line>() {
@Override
public int compare(Line o1, Line o2) {
if (o1.getTimestamp() < o2.getTimestamp()) {
return 1;
} else {
return 0;
}
}
});
for (Line line: lines) {
for (Stroke stroke: line) {
stroke.draw(gc, colorPicker.getValue());
}
}
}
}

View file

@ -0,0 +1,43 @@
package fr.emiko.graphicsElement;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.Vector;
public class Line extends Vector<Stroke> {
private int timestamp;
private int layer;
private javafx.scene.paint.Color color;
public JSONObject toJSONObject() {
JSONArray jsonArray = new JSONArray();
for (Stroke stroke: this) {
jsonArray.put(stroke.toJSON());
}
return new JSONObject().put("line", jsonArray);
}
public static Line fromJSONArray(JSONArray jsonArray) {
Line line = new Line();
for (int i = 0; i < jsonArray.length(); i++) {
line.add(Stroke.fromJSON(jsonArray.getString(i)));
}
return line;
}
public int getTimestamp() {
return timestamp;
}
public void setTimestamp(int timestamp) {
this.timestamp = timestamp;
}
public int getLayer() {
return layer;
}
public void setLayer(int layer) {
this.layer = layer;
}
}

View file

@ -1,11 +1,14 @@
package fr.emiko.graphicsElement;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.shape.Path;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import org.json.JSONObject;
import java.awt.*;
import java.util.Objects;
public class Stroke {
private final double fromX;
@ -13,17 +16,31 @@ public class Stroke {
private final double toX;
private final double toY;
private final double brushSize;
private final Color color;
public Stroke (double fromX, double fromY, double toX, double toY, double brushSize) {
public Stroke (double fromX, double fromY, double toX, double toY, double brushSize, Color color) {
this.fromX = fromX;
this.fromY = fromY;
this.toX = toX;
this.toY = toY;
this.brushSize = brushSize;
this.color = color;
}
public void draw (GraphicsContext g) {
g.setStroke(javafx.scene.paint.Color.BLACK);
public static Stroke fromJSON(String jsonStroke) {
JSONObject jsonObject = new JSONObject(jsonStroke);
return new Stroke(
jsonObject.getDouble("fromX"),
jsonObject.getDouble("fromY"),
jsonObject.getDouble("toX"),
jsonObject.getDouble("toY"),
jsonObject.getDouble("brushSize"),
(Color) jsonObject.get("color")
);
}
public void draw (GraphicsContext g, javafx.scene.paint.Color color) {
g.setStroke(color);
g.setLineCap(StrokeLineCap.ROUND);
g.setMiterLimit(1);
g.setLineWidth(brushSize);
@ -38,6 +55,34 @@ public class Stroke {
@Override
public String toString () {
return "Stroke{fromX=%f, fromY=%f, toX=%f, toY=%f, brushSize=%f}".formatted(fromX, fromY, toX, toY, brushSize);
// return "Stroke{fromX=%f, fromY=%f, toX=%f, toY=%f, brushSize=%f}".formatted(fromX, fromY, toX, toY, brushSize);
return this.toJSON();
}
public JSONObject toJSONObject() {
return new JSONObject()
.put("fromX", fromX)
.put("fromY", fromY)
.put("toX", toX)
.put("toY", toY)
.put("brushSize", brushSize)
.put("color", color);
}
public String toJSON() {
return toJSONObject().toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Stroke stroke = (Stroke) o;
return Double.compare(fromX, stroke.fromX) == 0 && Double.compare(fromY, stroke.fromY) == 0 && Double.compare(toX, stroke.toX) == 0 && Double.compare(toY, stroke.toY) == 0 && Double.compare(brushSize, stroke.brushSize) == 0;
}
public Color getColor() {
return this.color;
}
}

View file

@ -0,0 +1,175 @@
package fr.emiko.net;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* Client TCP : envoie des chaines de caractères à un serveur et lit les chaines en retour.
*/
public class ClientTCP {
/** Couleur rouge */
public static final String RED = "\u001b[31m";
/** Couleur bleue */
public static final String BLUE = "\u001b[34m";
/** Couleur standard */
public static final String RST = "\u001b[0m";
/** Fin de message */
public static final String END_MESSAGE = "fin";
/**
* Socket connecté au serveur
*/
protected Socket sock;
/**
* Flux de caractères UTF-8 en sortie
*/
protected PrintStream out;
/**
* Flux de caractères UTF-8 en entrée
*/
protected BufferedReader in;
/**
* Chaine de caractères "ip:port" du client
*/
protected String ipPort;
/**
* Le client est-il connecté ?
*/
protected boolean connected;
/**
* Programme principal [Déprécié]
* @param args Arguments
* @throws Exception Si la connexion échoue
*/
public static void main(String[] args) throws Exception {
ClientTCP client = new ClientTCP("localhost", 2024);
Thread envoi = new Thread(client::sendLoop);
Thread reception = new Thread(client::receiveLoop);
envoi.start();
reception.start();
envoi.join();
client.close();
}
/**
* Le constructeur ouvre la connexion TCP au serveur <code>host:port</code>
* et récupère les flux de caractères en entrée {@link #in} et sortie {@link #out}
*import static rtgre.chat.ChatApplication.LOGGER;
* @param host IP ou nom de domaine du serveur
* @param port port d'écoute du serveur
* @throws IOException si la connexion échoue ou si les flux ne sont pas récupérables
*/
public ClientTCP(String host, int port) throws IOException {
sock = new Socket(host, port);
ipPort = "%s:%d".formatted(sock.getLocalAddress().getHostAddress(), sock.getLocalPort());
this.connected = true;
OutputStream os = sock.getOutputStream();
InputStream is = sock.getInputStream();
out = new PrintStream(os, true, StandardCharsets.UTF_8);
in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8), 2048);
Thread rcLoop = new Thread(this::receiveLoop);
rcLoop.setDaemon(true);
rcLoop.start();
}
/**
* Envoie une chaine de caractères
*
* @param message chaine de caractères à transmettre
* @throws IOException lorsqu'une erreur sur le flux de sortie est détectée
*/
public void send(String message) throws IOException {
out.println(message);
if (out.checkError()) {
throw new IOException("Output stream error");
}
}
/**
* Getter de connected
* @return L'état de connected
*/
public boolean isConnected() {
return connected;
}
/**
* Setter de connected
* @param connected L'utilisateur est-il connecté ?
*/
public void setConnected(boolean connected) {
this.connected = connected;
}
/**
* Attente d'une chaine de caractères en entrée.
*
* @return chaine de caractères reçue
* @throws IOException lorsque la fin du flux est atteinte
*/
public String receive() throws IOException {
String message = in.readLine();
if (message == null) {
throw new IOException("End of the stream has been reached");
}
return message;
}
/**
* Fermeture de la connexion TCP
*/
public void close() {
try {
sock.close();
this.connected = false;
} catch (IOException e) {
}
}
/**
* Boucle d'envoi de messages
*/
public void sendLoop() {
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
connected = true;
try {
while (connected) {
String message = stdIn.readLine();
if (message == null) { // fin du flux stdIn
message = END_MESSAGE;
}
this.send(message);
if (END_MESSAGE.equals(message)) {
connected = false;
}
}
} catch (IOException e) {
connected = false;
}
}
/**
* Boucle de réception de messages
*/
public void receiveLoop() {
connected = true;
try {
while (connected) {
String message = this.receive();
System.out.println("Message received: " + message);
}
} catch (IOException e) {
connected = false;
}
}
}

View file

@ -0,0 +1,68 @@
package fr.emiko.net;
import fr.emiko.graphicalapp.HelloController;
import javafx.application.Platform;
import org.json.JSONObject;
import java.io.IOException;
public class DrawClient extends ClientTCP{
private final HelloController listener;
public DrawClient(String host, int port, HelloController listener) throws IOException {
super(host, port);
this.listener = listener;
}
/**
* Envoi d'un évènement, sérialisé dans sa représentation JSON, au serveur.
* @param event L'évènement à envoyer
*/
public void sendEvent(Event event) {
connected = true;
try {
String message = event.toJSON();
if (message == null) { // fin du flux stdIn
message = END_MESSAGE;
}
this.send(message);
if (END_MESSAGE.equals(message)) {
connected = false;
}
} catch (IOException e) {
connected = false;
}
}
/**
* Boucle de réception des messages : chaque message est un évènement sérialisé en JSON, qui est transféré à ChatController.handleEvent(rtgre.modeles.Event) pour traitement.
* Si le message n'est pas conforme (format JSON), la connection est stoppée.
*/
@Override
public void receiveLoop() {
try {
while (connected) {
String message = this.receive();
if (listener != null) {
Platform.runLater(() -> listener.handleEvent(Event.fromJSON(message)));
}
}
} catch (IOException e) {
connected = false;
} finally {
close();
}
}
public void sendAuthEvent(String login) {
try {
this.send(new Event(Event.AUTH, new JSONObject().put("username", login)).toJSON());
} catch (IOException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,234 @@
package fr.emiko.net;
import java.awt.*;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Vector;
import fr.emiko.graphicsElement.Line;
import org.json.JSONException;
import org.json.JSONObject;
public class DrawServer {
private ServerSocket passiveSocket;
private Vector<DrawClientHandler> clientList = new Vector<DrawClientHandler>();
private Vector<Line> lines;
public DrawServer(int port) throws IOException {
passiveSocket = new ServerSocket(port);
}
public static void main(String[] args) throws IOException {
DrawServer server = new DrawServer(8090);
server.acceptClients();
}
public void acceptClients() {
while (true) {
try {
Socket sock = passiveSocket.accept();
handleNewClient(sock);
} catch (IOException e) {
System.out.println(e);
}
}
}
public void removeClient(DrawClientHandler client) {
clientList.remove(client);
}
private void handleNewClient(Socket sock) throws IOException {
DrawClientHandler client = new DrawClientHandler(sock);
clientList.add(client);
Thread clientLoop = new Thread(client::eventReceiveLoop);
clientLoop.start();
}
/**
* Ferme la connexion du serveur, en fermant la connexion auprès de tous ses clients, puis en fermant son socket en écoute passive.
* @throws IOException si la connexion
*/
public void close() throws IOException {
for (DrawClientHandler client : clientList) {
client.close();
}
passiveSocket.close();
}
private class DrawClientHandler {
/** Message de fin d'une connexion */
public static final String END_MESSAGE = "fin";
/**
* Socket connecté au client
*/
private Socket sock;
/**
* Flux de caractères en sortie
*/
private PrintStream out;
/**
* Flux de caractères en entrée
*/
private BufferedReader in;
/**
* Chaine de caractères "ip:port" du client
*/
private String ipPort;
private User user;
/**
* Initialise les attributs {@link #sock} (socket connecté au client),
* {@link #out} (flux de caractères UTF-8 en sortie) et
* {@link #in} (flux de caractères UTF-8 en entrée).
*
* @param sock socket connecté au client
* @throws IOException si la connexion ne peut être établie ou si les flux ne peuvent être récupérés
*/
public DrawClientHandler(Socket sock) throws IOException {
this.sock = sock;
this.ipPort = "%s:%d".formatted(sock.getInetAddress().getHostAddress(), sock.getPort());
OutputStream os = sock.getOutputStream();
InputStream is = sock.getInputStream();
this.out = new PrintStream(os, true, StandardCharsets.UTF_8);
this.in = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
}
/**
* Boucle de réception d'évènement : réceptionne les messages reçus et les délèguent à `handleEvent(java.lang.String)` pour les interpréter
*/
public void eventReceiveLoop() {
try {
String message = null;
while (!END_MESSAGE.equals(message)) {
message = in.readLine();
if (message == null) {
break;
}
System.out.println("Réception de message : " + message);
try {
if (!handleEvent(message)) {
break;
}
} catch (Exception e) {
System.out.println(e.getMessage());
break;
}
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
close();
}
/**
* Traitement d'un évènement. Ventile vers les méthodes traitant chaque type d'évènement.
* @param message objet évènement sous la forme d'une chaine JSON brute de réception
* @return `false` si l'évènement est de type Event.QUIT , `true` pour tous les autres types.
* @throws JSONException si l'objet JSON n'est pas conforme
* @throws IllegalStateException si l'authentification n'est pas effectuée
*/
private boolean handleEvent(String message) throws JSONException, IllegalStateException {
Event event = Event.fromJSON(message);
switch (event.getType()) {
case Event.AUTH -> {
doLogin(event.getContent());
return true;
}
case Event.ADDLINE -> {
doAddLine(event.getContent());
return true;
}
case Event.DELLINE -> {
doDelLine(event.getContent());
return true;
}
case Event.LSTLINE -> {
doSendLines();
return true;
}
default -> {
return false;
}
}
}
private void doDelLine(JSONObject content) {
Line line = Line.fromJSONArray(content.getJSONArray("line"));
this.user.getLines().remove(line);
sendAllOtherUsers(new Event("DELLINE", line.toJSONObject()));
}
private void doAddLine(JSONObject content) {
try {
System.out.println(Line.fromJSONArray(content.getJSONArray("line")));
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
}
Line line = Line.fromJSONArray(content.getJSONArray("line"));
this.user.getLines().add(line);
sendAllOtherUsers(new Event("LINE", line.toJSONObject()));
}
private void sendAllOtherUsers(Event event) {
for (DrawClientHandler client : clientList) {
if (client.user != this.user) {
System.out.println(client.user.getUsername());
sendEvent(client, event);
}
}
}
private void sendEvent(DrawClientHandler client, Event event) {
String jsonEvent = event.toJSON();
client.out.println(jsonEvent);
}
private void doSendLines() {
Vector<Line> lines = new Vector<>();
for (DrawClientHandler client: clientList) {
for (Line line: client.user.getLines()) {
lines.add(line);
}
}
for (Line line: lines) {
out.println(new Event("LINELST", line.toJSONObject()));
}
}
private void doLogin(JSONObject content) {
this.user = new User(content.getString("username"));
}
public void close() {
try {
sock.close();
removeClient(this);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View file

@ -0,0 +1,50 @@
package fr.emiko.net;
import org.json.JSONObject;
public class Event {
public static final String AUTH = "AUTH";
public static final String LSTLINE = "LSTLINE";
public static final String ADDLINE = "ADDLINE";
public static final String DELLINE = "DELLINE";
public static final String LINE = "LINE";
public static final String LINELST = "LINELST";
private String type;
private JSONObject content;
public Event(String type, JSONObject content) {
this.type = type;
this.content = content;
}
public String getType() {
return type;
}
public JSONObject getContent() {
return content;
}
public JSONObject toJSONObject() {
return new JSONObject()
.put("type", type)
.put("content", content);
}
public String toJSON() {
return toJSONObject().toString();
}
public static Event fromJSON(String obj) {
JSONObject jobj = new JSONObject(obj);
String type = jobj.getString("type");
JSONObject content = jobj.getJSONObject("content");
return new Event(type, content);
}
@Override
public String toString() {
return this.toJSON();
}
}

View file

@ -0,0 +1,32 @@
package fr.emiko.net;
import fr.emiko.graphicsElement.Line;
import java.util.Vector;
public class User {
private String username;
private String hashedPassword = "";
private Vector<Line> lines = new Vector<Line>();
private boolean connected;
public User(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Vector<Line> getLines() {
return lines;
}
public void setLines(Vector<Line> lines) {
this.lines = lines;
}
}

View file

@ -4,6 +4,7 @@ module fr.emiko.graphicalapp {
requires java.desktop;
requires org.controlsfx.controls;
requires net.synedra.validatorfx;
requires org.json;
opens fr.emiko.graphicalapp to javafx.fxml;

View file

@ -2,15 +2,20 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.canvas.Canvas?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ColorPicker?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.Menu?>
<?import javafx.scene.control.MenuBar?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.Separator?>
<?import javafx.scene.control.Slider?>
<?import javafx.scene.control.SplitPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
@ -24,6 +29,14 @@
<MenuItem fx:id="newCanvasButton" mnemonicParsing="false" text="New canvas" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Connect">
<items>
<MenuItem fx:id="hostButton" mnemonicParsing="false" text="Host" />
<MenuItem fx:id="joinButton" mnemonicParsing="false" text="Join" />
<MenuItem fx:id="disconnectButton" mnemonicParsing="false" text="Disconnect" />
<MenuItem fx:id="stopHostButton" mnemonicParsing="false" text="Stop host" />
</items>
</Menu>
<Menu mnemonicParsing="false" text="Edit">
<items>
<MenuItem fx:id="saveButton" mnemonicParsing="false" text="Save" />
@ -37,9 +50,9 @@
</Menu>
</menus>
</MenuBar>
<SplitPane dividerPositions="0.16948784722222218" VBox.vgrow="ALWAYS">
<SplitPane fx:id="mainPane" dividerPositions="0.16948784722222218">
<items>
<VBox prefHeight="200.0" prefWidth="100.0">
<VBox prefHeight="200.0" prefWidth="100.0" spacing="10.0">
<children>
<GridPane>
<columnConstraints>
@ -71,10 +84,27 @@
<Slider fx:id="brushSizeSlider" blockIncrement="1.0" majorTickUnit="5.0" max="10.0" minorTickCount="9" showTickLabels="true" showTickMarks="true" snapToTicks="true" GridPane.rowIndex="1" />
</children>
</GridPane>
<ColorPicker fx:id="colorPicker" editable="true" />
<Separator />
<GridPane>
<columnConstraints>
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints halignment="CENTER" hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Button fx:id="addLayerButton" minHeight="25.0" minWidth="25.0" mnemonicParsing="false" text="+" />
<Button fx:id="removeLayerButton" minHeight="25.0" minWidth="25.0" mnemonicParsing="false" text="-" GridPane.columnIndex="1" />
</children>
</GridPane>
<ListView fx:id="layerListView" prefHeight="200.0" prefWidth="200.0" />
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></VBox>
</padding>
</VBox>
<ScrollPane fx:id="scrollPane" cache="true" cacheHint="QUALITY" depthTest="ENABLE" hbarPolicy="ALWAYS" style="-fx-background-color: #666666; -fx-background: #666666;" vbarPolicy="ALWAYS">
<content>
<GridPane>
@ -96,5 +126,14 @@
</ScrollPane>
</items>
</SplitPane>
<HBox>
<children>
<Label text="Status : " />
<Label text="Connected to username@localhost:8090" />
</children>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
</HBox>
</children>
</VBox>