Skip to content
Snippets Groups Projects
Select Git revision
  • 2686cac921a6a07b4e522be080e2adf43d9b74e1
  • master default protected
  • devel protected
  • weather_stuff
  • open_transport_stuff
  • gui-mq
  • gui-transport
  • dev_messageQueue
  • gui
9 results

MainWindowController.java

Blame
  • 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 :");
            });
        }
    }