Select Git revision
MainWindowController.java
MainWindowController.java 19.00 KiB
package ch.hepia.ui;
import ch.hepia.Main;
import ch.hepia.api.transport.*;
import ch.hepia.api.weather.WeatherAPI;
import ch.hepia.config.AppConfig;
import ch.hepia.config.AppContext;
import ch.hepia.events.ChatMessage;
import ch.hepia.events.Event;
import ch.hepia.events.JoinedJourney;
import ch.hepia.events.LeftJourney;
import ch.hepia.models.User;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.geometry.Insets;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.util.Duration;
import java.io.IOException;
import java.net.URL;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.Predicate;
public class MainWindowController implements Initializable {
@FXML
private Label currentJourneyLabel;
@FXML
private Label startStopLabel;
@FXML
private ComboBox<String> originComboBox;
@FXML
private ComboBox<String> destinationComboBox;
@FXML
private Canvas connectionCanvas;
@FXML
private Button searchOriginButton;
@FXML
private Button searchDestinationButton;
@FXML
private Button launchItineraryButton;
@FXML
private Button sendMessageButton;
@FXML
private TextField messageTextBox;
@FXML
private Pane chatContainer;
@FXML
private Pane connectionContainer;
// OWN ATTRIBUTES
private List<Connection> displayedConnections;
private Connection currentJourney;
/**
* Shows a sad message when the API/app crashes.
*/
private void showSadMessage(String text){
UiUtils.dialog(Alert.AlertType.ERROR, "Erreur",
"Une erreur est survenue.", text);
}
/**
* Searches stops that matches the query.
* @param newValue The stop name query.
* @param transportApi The API to connect to
* @param target The combo box where to put the results.
*/
private void searchStops(String newValue, LinkAPI transportApi, ComboBox<String> target){
if (!newValue.isEmpty() && newValue.length() > 3 && !target.getItems().contains(newValue)){
try {
List<Location> locations = transportApi.getStations(newValue);
ArrayList<String> results = new ArrayList<>();
locations.forEach((location) -> results.add(location.getName()));
target.getItems().clear();
target.getItems().addAll(results);
target.show();
} catch (IOException e) {
showSadMessage(AppConfig.ERROR_API_UNREACHABLE);
}
}
}
/**
* Draws a connection between two places. Basically lines.
* @param connection The connection to draw
* @param x Where to draw
* @param y Where to draw but vertically
*/
private void drawConnection(Connection connection, int x, int y){
List<Section> sections = connection.getSections();
GraphicsContext gcx = connectionCanvas.getGraphicsContext2D();
//Draw starting station
gcx.setFill(Color.RED);
gcx.fillText(sections.get(0).getDeparture().getLocation().getName(), x, y - 20);
gcx.setFill(Color.BLACK);
gcx.fillText(sections.get(0).getDeparture().getDepartureTime().toString(), x, y - 40);
gcx.setFill(Color.RED);
gcx.fillOval(x - 5, y - 5, 10, 10);
gcx.strokeLine(x, y, x + 622 / (sections.size() + 1), y);
for (int i = 0; i < sections.size(); i++){
//Draw each step
drawConnectionStep(sections.get(i), sections.size(), x, y, i);
}
}
/**
* Draws a connection step between two places.
* @param section The section to draw
* @param size Number of section in the connection
* @param x Where to draw
* @param y Where to draw but vertically
* @param cnt Section position in connection list.
*/
private void drawConnectionStep(Section section, int size, int x, int y, int cnt) {
GraphicsContext gcx = connectionCanvas.getGraphicsContext2D();
int xSize = (AppConfig.APP_MAIN_VIEW_WIDTH / (size + 1));
gcx.strokeLine( x + xSize * (cnt + 1), y, AppConfig.APP_MAIN_VIEW_WIDTH / (size + 1), y);
gcx.setFill(Color.RED);
gcx.fillOval(x + xSize * (cnt + 1) - 5, y - 5, 10, 10);
gcx.fillText(section.getArrival().getLocation().getName(), x + xSize * (cnt + 1), y - 20);
gcx.setFill(Color.BLACK);
gcx.fillText(section.getArrival().getArrivalTime().toString(), x + xSize * (cnt + 1), y - 40);
Journey jrn = section.getJourney();
String transportType = "MARCHE"; //Draw the type of transport used on previous step
if (!(jrn instanceof Journey.EmptyJourney)) {
transportType = jrn.getCategory() + ", " + jrn.getNumber();
}
gcx.setFill(Color.BLUE);
gcx.fillText(transportType,x + xSize * (cnt), y - 55);
gcx.setFill(Color.RED);
}
/**
* Draws a newly received message
* @param message The message to draw
*/
private void drawMessage(String message, String image, Color color){
Pane p = new Pane();
setChatPanelStyle(p, color);
Image lblImg = new Image(Main.class.getResourceAsStream(image));
Label msg = new Label();
msg.setWrapText(true);
msg.setStyle("-fx-padding: 8px");
msg.setText(message);
msg.setMaxWidth(310);
msg.setGraphic(new ImageView(lblImg));
p.getChildren().add(msg);
if (chatContainer.getChildren().size() >= 7) {
chatContainer.getChildren().remove(0);
}
insertMessageIntoQueue();
chatContainer.getChildren().add(p);
}
/**
* Style a chat panel before pushing
* @param p Panel to style
* @param color Background color of the gradient
*/
private void setChatPanelStyle(Pane p, Color color) {
p.setBackground(
new Background(
new BackgroundFill(
new LinearGradient(0, 0, 0, 1, true,
CycleMethod.NO_CYCLE,
new Stop(1, color),
new Stop(0, Color.WHITE)
),
new CornerRadii(5),
Insets.EMPTY)));
p.setBorder(new Border(new BorderStroke(
Color.color(0.6, 0.6, 0.6), BorderStrokeStyle.SOLID, new CornerRadii(5),
BorderWidths.DEFAULT)));
p.setPrefWidth(312);
p.setMaxWidth(320);
}
/**
* Style a connection panel
* @param p Panel to style
*/
private void setConnectionPanelStyle(Pane p) {
p.setBackground(
new Background(
new BackgroundFill(
new LinearGradient(0, 0, 0, 1, true,
CycleMethod.NO_CYCLE,
new Stop(1, AppConfig.COLOR_BLUE_10_OPACITY),
new Stop(0, Color.TRANSPARENT)
),
new CornerRadii(5),
Insets.EMPTY)));
p.setBorder(new Border(new BorderStroke(Color.color(0.6, 0.6, 0.6), BorderStrokeStyle.SOLID,
new CornerRadii(5), BorderWidths.DEFAULT)));
p.setPrefWidth(AppConfig.APP_MAIN_VIEW_WIDTH + 10);
p.setPrefHeight(90);
}
/**
* Wrapper for local chat message
* @param message Message to print
*/
private void drawHelpMessage(String message) {
drawMessage(message, AppConfig.HELP_MESSAGE_ICON, Color.color(1, 0.9, 0.5, 0.3));
}
/**
* Print available commands in the chat
*/
private void drawCommands(){
StringBuilder buffer = new StringBuilder("/"+AppConfig.CHAT_COMMANDS.get(0));
for(int i = 1; i < AppConfig.CHAT_COMMANDS.size(); i++){
buffer.append(", /").append(AppConfig.CHAT_COMMANDS.get(i));
}
drawHelpMessage(buffer.toString());
}
/**
* Moves the older messages downwards
*/
private void insertMessageIntoQueue(){
for (int i = 0; i < chatContainer.getChildren().size(); i++){
Pane sp = (Pane) chatContainer.getChildren().get(i);
TranslateTransition t = new TranslateTransition(Duration.seconds(0.25), sp);
t.setToY((chatContainer.getChildren().size() - i) * (sp.getHeight() + 4));
t.play();
}
}
/**
* Leaves a journey and broadcast it
* @param app The app context
*/
private void leaveJourney(AppContext app){
app.getMessageManager().sendLeftJourney(new LeftJourney(app.getUser().get(), currentJourney));
currentJourneyLabel.setText(AppConfig.DEFAULT_JOURNEY_TEXT);
try {
currentJourney = new Connection.EmptyConnection();
} catch (ParseException e){
showSadMessage("Quelque chose s'est mal passé en voulant quitter votre trajet.");
e.printStackTrace();
}
currentJourneyLabel.setUnderline(false);
}
/**
* Create a new journey and broadcast it
* Set the journey label and the current journey
* @param app App context
* @param pnl Panel containing the journey information
*/
private void createJourney(AppContext app, Pane pnl) throws IOException {
Integer pos = Integer.parseInt(pnl.getId());
WeatherAPI api = new WeatherAPI();
if (!(currentJourney instanceof Connection.EmptyConnection)){
leaveJourney(app);
}
Connection connection = displayedConnections.get(pos);
String weather = api.getWeatherFrom(connection.getFrom().toString()).getConditions();
JoinedJourney joinedJourney = new JoinedJourney(app.getUser().get(), connection, weather);
app.getMessageManager().sendJoinedJourney(joinedJourney);
currentJourney = displayedConnections.get(pos);
currentJourneyLabel.setText("Vous voyagez de " + currentJourney.getFrom().getLocation().getName() + " vers "
+ currentJourney.getTo().getLocation().getName() + ".");
currentJourneyLabel.setUnderline(true);
currentJourneyLabel.setOnMouseClicked(event -> {
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Quitter le trajet");
alert.setHeaderText("Quitter le trajet");
alert.setContentText("Souhaitez-vous quitter ce trajet ?");
ButtonType oui = new ButtonType("Oui");
ButtonType non = new ButtonType("Non");
alert.getButtonTypes().setAll(oui, non);
Optional<ButtonType> result = alert.showAndWait();
if (result.get().equals(oui)){
leaveJourney(app);
}
});
}
/**
* Parse the line and search for chat command.
* @param cmd The line to parse.
*/
private void parseChatCommand(String cmd){
String[] command = cmd.split(" ", 2);
Color c = Color.color(1, 0.9, 0.5, 0.3);
if (AppConfig.CHAT_COMMANDS.contains(command[0])){
switch (command[0]) {
case "help":
drawCommands();
break;
case "block":
if (command.length > 1) {
Main.getContext().getUser().get().addIgnoredUser(new User(command[1]));
drawMessage(command[1] + " bloqué.", AppConfig.HELP_MESSAGE_ICON, c);
}
break;
case "blacklist":
List<User> list = Main.getContext().getUser().get().getIgnoredUserList();
if (list.size() > 0) {
StringBuilder buffer = new StringBuilder(list.get(0).getName());
for (int i = 1; i < list.size(); i++){
buffer.append(", ").append(list.get(i).getName());
}
drawMessage("Bloqués : " + buffer.toString(),
AppConfig.HELP_MESSAGE_ICON, c);
}
break;
case "unblock":
List<User> ulist = Main.getContext().getUser().get().getIgnoredUserList();
ulist.remove(new User(command[1]));
break;
}
} else {
drawCommands();
}
}
/**
* Initializes the event handler for every textbox/combofield that has a "press enter" behaviour.
* @param app The app context
* @param transportApi The transport API
*/
private void initializeTextFieldWithEnterBehaviour(AppContext app, LinkAPI transportApi){
UiUtils.buttonWhenEnter(originComboBox, searchOriginButton);
UiUtils.buttonWhenEnter(destinationComboBox, searchDestinationButton);
UiUtils.buttonWhenEnter(messageTextBox, sendMessageButton);
searchOriginButton.setOnAction(event -> {
originComboBox.setValue(originComboBox.getEditor().getText());
searchStops(originComboBox.getValue(), transportApi, originComboBox);
});
searchDestinationButton.setOnAction(event -> {
destinationComboBox.setValue(destinationComboBox.getEditor().getText());
searchStops(destinationComboBox.getValue(), transportApi, destinationComboBox);
});
sendMessageButton.setOnAction(event -> {
if (app.getUser().isPresent() && !messageTextBox.getText().isEmpty()){
if ( messageTextBox.getText().charAt(0) != '/') {
app.getMessageManager().sendChatMessage(new ChatMessage(app.getUser().get(), messageTextBox.getText()));
} else {
parseChatCommand(messageTextBox.getText().substring(1));
}
messageTextBox.clear();
}
});
}
/**
* Handle event subscriptions.
* @param app The app context
*/
private void subscribeToEvents(AppContext app){
Predicate<Event> userFilter = event -> !(app.getUser().get().getIgnoredUserList().contains(event.getUser()));
// Subscribe to chat message
app.getMessageManager().conditionalSubscribeChatMessage(chatMessage -> Platform.runLater(() -> {
User sender = chatMessage.getUser();
String message = sender.getName() + ": " + chatMessage.getMessage();
if (sender.equals(app.getUser().get())) {
drawMessage(message, AppConfig.CHAT_MESSAGE_ICON_SELF, AppConfig.COLOR_BLUE_10_OPACITY);
} else {
drawMessage(message, AppConfig.CHAT_MESSAGE_ICON, AppConfig.COLOR_BLUE_10_OPACITY);
}
}), userFilter::test);
// Subscribe to joined journey
app.getMessageManager().conditionalSubscribeJoinedJourney(joinedJourney -> Platform.runLater(() -> {
User sender = joinedJourney.getUser();
String message = sender.getName() + " voyage !";
if (sender.equals(app.getUser().get())) {
drawMessage(message, AppConfig.CHAT_MESSAGE_ICON_SELF, AppConfig.COLOR_BLUE_10_OPACITY);
} else {
drawMessage(message, AppConfig.CHAT_MESSAGE_ICON, AppConfig.COLOR_BLUE_10_OPACITY);
}
}), userFilter::test);
// Subscribe to left journey
app.getMessageManager().conditionalSubscribeLeftJourney(leftJourney -> Platform.runLater(() -> {
User sender = leftJourney.getUser();
String message = sender.getName() + " a terminé son voyage !";
if (sender.equals(app.getUser().get())) {
drawMessage(message, AppConfig.CHAT_MESSAGE_ICON_SELF, AppConfig.COLOR_BLUE_10_OPACITY);
} else {
drawMessage(message, AppConfig.CHAT_MESSAGE_ICON, AppConfig.COLOR_BLUE_10_OPACITY);
}
}), userFilter::test);
}
/**
* Sets the form up
* @param url The JavaFX URL handler
* @param resourceBundle The JavaDX ResB handle
*/
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
LinkAPI transportApi = new LinkAPI();
AppContext app = Main.getContext();
currentJourneyLabel.setText(AppConfig.DEFAULT_JOURNEY_TEXT);
startStopLabel.setText(""); // No text should be visible when no journey has been selected.
messageTextBox.textProperty().addListener((ov, oldValue, newValue) -> {
if (messageTextBox.getText().length() > 60) {
messageTextBox.setText(messageTextBox.getText().substring(0, 60));
}
});
initializeTextFieldWithEnterBehaviour(app, transportApi);
try {
currentJourney = new Connection.EmptyConnection();
} catch (ParseException e){
showSadMessage("Impossible de démarrer proprement l'application.");
e.printStackTrace();
}
launchItineraryButton.setOnAction(event -> {
try {
connectionCanvas.getGraphicsContext2D()
.clearRect(0, 0, connectionCanvas.getWidth(), connectionCanvas.getHeight());
connectionContainer.getChildren().removeIf(c -> c instanceof Pane);
displayedConnections = transportApi.getConnections(
originComboBox.getValue(), destinationComboBox.getValue());
startStopLabel.setText(originComboBox.getValue() + " - " + destinationComboBox.getValue());
for (int i = 0; i < displayedConnections.size(); i++){
// Now iterating over connections
drawConnection(displayedConnections.get(i), 30, 100 + 100 * i);
Pane pane = new Pane();
setConnectionPanelStyle(pane);
pane.setTranslateX(20);
pane.setTranslateY(85 + 100 * i);
pane.setId(Integer.toString(i));
Platform.runLater(() -> {
connectionContainer.getChildren().add(pane);
pane.setOnMouseClicked(e -> {
Pane pnl = (Pane) e.getSource();
try {
createJourney(app, pnl);
} catch (IOException ex){
showSadMessage("Une erreur s'est produite lors de la publication de cet évènement.");
ex.printStackTrace();
}
});
});
}
} catch (IOException e) {
showSadMessage(AppConfig.ERROR_API_UNREACHABLE);
}
});
subscribeToEvents(app);
// Login messages:
drawCommands();
Platform.runLater(() -> {
drawHelpMessage("Bonjour " + app.getUser().get().getName() + " ! Les commandes à dispositions sont :");
});
}
}