/* * The MIT License * * Copyright 2014 Ryan Gilera ryangilera@gmail.com. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package com.github.daytron.flipit.map.creator.controller; import com.github.daytron.flipit.map.creator.MainApp; import com.github.daytron.flipit.map.creator.utility.GlobalSettings; import com.github.daytron.flipit.map.creator.model.Map; import com.github.daytron.flipit.map.creator.model.GraphicsManager; import com.github.daytron.flipit.map.creator.model.LogManager; import com.github.daytron.flipit.map.creator.model.MapManager; import com.github.daytron.flipit.map.creator.utility.StringUtils; import com.github.daytron.simpledialogfx.data.DialogResponse; import com.github.daytron.simpledialogfx.data.DialogText; import com.github.daytron.simpledialogfx.data.DialogType; import com.github.daytron.simpledialogfx.dialog.Dialog; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import java.util.logging.Level; import java.util.logging.Logger; import javafx.application.Platform; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.ObservableList; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Node; import javafx.scene.canvas.Canvas; import javafx.scene.control.Button; import javafx.scene.control.ComboBox; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.stage.FileChooser; import javafx.stage.WindowEvent; /** * The controller class that manages and handles all of application's user * events. * * @author Ryan Gilera */ public class ViewController implements Initializable { @FXML private ComboBox<Integer> row_combo; @FXML private ComboBox<Integer> column_combo; @FXML private Button generate_map_btn; @FXML private TextField title_field; @FXML private Button player1_start_btn; @FXML private Button player2_start_btn; @FXML private Button boulder_btn; @FXML private Canvas canvas; @FXML private AnchorPane pane_canvas; @FXML private Button neutral_btn; @FXML private TextArea logArea; @FXML private MenuBar menubar; @FXML private MenuItem menuFileNew; @FXML private MenuItem menuFileOpen; @FXML private MenuItem menuFileSave; @FXML private MenuItem menuHelpAbout; @FXML private MenuItem menuFileQuit; @FXML private MenuItem menuLogClear; @FXML private Menu menuFileRecent; // MainApp object private MainApp app; // Flag for letting canvas know an object can be applied to the map private boolean isEditMapOn; // The key word for the canvas to know which button is pressed and // apply necessary tile modification when user click the canvas private String tileToEdit; // Map private Map map; private File currentFileOpened; // Map variables private int numberOfRows; private int numberOfColumns; // Tile variables private List<Integer[]> listOfBoulders; // Flag to differentiate from open and new map private boolean isOpeningAMap = false; // Flag to know if current map is save or not private boolean isCurrentMapSave = true; // Flag to know if no map is opened or generated private boolean isThereAMapVisible = false; // List of opened map files private List<File> listOfRecentFiles; private GraphicsManager graphicsManager; private LogManager logManager; /** * An override method implemented from Initializable interface. Initialize * all necessary configurations in launching the application's view. * * @param url The location used to resolve relative paths for the root * object, or null if the location is not known. * @param rb The resources used to localize the root object, or null if the * root object was not localized. */ @Override public void initialize(URL url, ResourceBundle rb) { // ################## INIT #################// // Get the only instance of GraphicsManager this.graphicsManager = GraphicsManager.getInstance(); this.graphicsManager.init(this.canvas, this.map); // Get the only instance of LogManager this.logManager = LogManager.getInstance(); this.logManager.init(this.logArea); // Resets the flag for detecting button pressed from objects this.isEditMapOn = false; // Init listener for log textarea to autoscroll bottom this.logArea.textProperty().addListener(new ChangeListener<Object>() { @Override public void changed(ObservableValue<?> observable, Object oldValue, Object newValue) { logArea.setScrollTop(Double.MAX_VALUE); //this will scroll to the bottom // Can use Double.MIN_VALUE to scroll to the top } }); // Add Keyboard shortcuts this.menuFileNew.setAccelerator(new KeyCodeCombination(KeyCode.N, KeyCodeCombination.CONTROL_DOWN)); this.menuFileOpen.setAccelerator(new KeyCodeCombination(KeyCode.O, KeyCodeCombination.CONTROL_DOWN)); this.menuFileSave.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCodeCombination.CONTROL_DOWN)); this.menuFileQuit.setAccelerator(new KeyCodeCombination(KeyCode.Q, KeyCodeCombination.CONTROL_DOWN)); this.menuLogClear.setAccelerator(new KeyCodeCombination(KeyCode.C, KeyCodeCombination.CONTROL_DOWN, KeyCodeCombination.SHIFT_DOWN)); this.menuHelpAbout.setAccelerator(new KeyCodeCombination(KeyCode.A, KeyCodeCombination.ALT_DOWN)); // Initialize list of recent map files this.listOfRecentFiles = new ArrayList<>(); // ################## MAP SIZE COMBO BOXES #################// // Define column list items ObservableList<Integer> columnOptions = (ObservableList<Integer>) GlobalSettings.LIST_POSSIBLE_TILES; // Define row list items ObservableList<Integer> rowOptions = (ObservableList<Integer>) GlobalSettings.LIST_POSSIBLE_TILES; // Attach lists to their corresponding combo object this.column_combo.setItems(columnOptions); this.row_combo.setItems(rowOptions); // Set default value for each combobox this.column_combo.getSelectionModel().select( GlobalSettings.COLUMN_DEFAULT_VALUE); // Saves current column selection this.numberOfColumns = (int) GlobalSettings.COLUMN_DEFAULT_VALUE; this.row_combo.getSelectionModel().select( GlobalSettings.ROW_DEFAULT_VALUE); // Saves current row selection this.numberOfRows = (int) GlobalSettings.ROW_DEFAULT_VALUE; // Apply listeners this.column_combo.valueProperty().addListener(new ChangeListener<Integer>() { @Override public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue) { column_combo.getSelectionModel().select(newValue); numberOfColumns = (int) newValue; } }); this.row_combo.valueProperty().addListener(new ChangeListener<Integer>() { @Override public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue) { row_combo.getSelectionModel().select(newValue); numberOfRows = (int) newValue; } }); } /** * Generates a map with no arguments. This is called only when Generate * button or File>New menu item is clicked. */ private void generateMap() { this.generateMap(""); } /** * Overloads generateMap method with path as an argument. This is called * only when File>Open event starts. * * @param path The full directory path of the newly opened map. */ private void generateMap(String path) { // ################## INIT #################// // Resets any previous toggles of this flag // Use to notify player/object button is clicked if true. this.isEditMapOn = false; if (!this.isOpeningAMap) { // Resets list of boulders and new map // only if it is a new map this.listOfBoulders = new ArrayList<>(); this.map = new Map(); } // Apply appropriate edge effect if (this.numberOfColumns > 8 && this.numberOfRows > 8) { this.graphicsManager.setTileEdgeEffect(2); } else { this.graphicsManager.setTileEdgeEffect(4); } // ################## GENERATE MAP #################// this.graphicsManager.generateMap(this.numberOfRows, this.numberOfColumns, this.map); // String log message String msgMapDrawnLog; if (!this.isOpeningAMap) { // Calls graphics manager to draw new map this.graphicsManager.drawNewMap(); // Prepare log message msgMapDrawnLog = GlobalSettings.LOG_NEW_MAP + this.numberOfColumns + " columns & " + this.numberOfRows + " rows"; } else { // Calls graphics manager to draw opened map this.graphicsManager.drawOpenMap(); // Paint tile for player 1 start position this.graphicsManager.paintPlayerStart(1, this.map.getListOfPlayer1StartPosition()[0], this.map.getListOfPlayer1StartPosition()[1]); // Paint tile for player 2 start position this.graphicsManager.paintPlayerStart(2, this.map.getListOfPlayer2StartPosition()[0], this.map.getListOfPlayer2StartPosition()[1]); // Prepare log message msgMapDrawnLog = GlobalSettings.LOG_OPEN_MAP + this.numberOfColumns + " columns & " + this.numberOfRows + " rows" + "\nMap file opened: " + path; } // Notify user in log area this.logManager.addNewLogMessage(msgMapDrawnLog); // Toggles a flag to know a map is now visible from the canvas. // This is use for preventing the object buttons (player, boulderm // and neutral tiles) to initiate // Alternatively the condition (this.map != null) can also be used) // But a more verbose variable name is better suited this.isThereAMapVisible = true; } /** * The event handler when Generate button is clicked. * * @param event The ActionEvent object */ @FXML private void generateBtnOnClick(ActionEvent event) { // Confirmation dialog before proceeding // Checks for any unsave previous map if (!isCurrentMapSave) { Dialog dialog = new Dialog(DialogType.CONFIRMATION, GlobalSettings.DIALOG_NEW_MAP_HEAD_MSG_NOT_SAVE, GlobalSettings.DIALOG_NEW_MAP_BODY_MSG_NOT_SAVE); if (dialog.getResponse() == DialogResponse.OK) { // Cancel opening file if user press cancel return; } } // Toggle flag if the map being generated is from opening a file or new this.isOpeningAMap = false; this.generateMap(); // Toggle flag for detecting unsave map data this.isCurrentMapSave = false; } /** * The event handler when player 1 start button is clicked. * * @param event The ActionEvent object */ @FXML private void player1StartBtnOnClick(ActionEvent event) { if (this.isThereAMapVisible) { this.isEditMapOn = true; this.tileToEdit = GlobalSettings.TILE_PLAYER1; this.logManager.addNewLogMessage(GlobalSettings.LOG_PLAYER1_ON); } else { String noMapMsg = GlobalSettings.LOG_WARNING + GlobalSettings.LOG_GENERATE_OPEN_MAP_FIRST; this.logManager.addNewLogMessage(noMapMsg); } } @FXML private void player2StartBtnOnClick(ActionEvent event) { if (this.isThereAMapVisible) { this.isEditMapOn = true; this.tileToEdit = GlobalSettings.TILE_PLAYER2; this.logManager.addNewLogMessage(GlobalSettings.LOG_PLAYER2_ON); } else { String noMapMsg = GlobalSettings.LOG_WARNING + GlobalSettings.LOG_GENERATE_OPEN_MAP_FIRST; this.logManager.addNewLogMessage(noMapMsg); } } /** * The event handler when Boulder button is clicked. * * @param event The ActionEvent object */ @FXML private void boulderBtnOnClick(ActionEvent event) { if (this.isThereAMapVisible) { this.isEditMapOn = true; this.tileToEdit = GlobalSettings.TILE_BOULDER; this.logManager.addNewLogMessage(GlobalSettings.LOG_BOULDER_ON); } else { String noMapMsg = GlobalSettings.LOG_WARNING + GlobalSettings.LOG_GENERATE_OPEN_MAP_FIRST; this.logManager.addNewLogMessage(noMapMsg); } } /** * The event handler when Neutral button is clicked. * * @param event The ActionEvent object */ @FXML private void neutralBtnOnClick(ActionEvent event) { if (this.isThereAMapVisible) { this.isEditMapOn = true; this.tileToEdit = GlobalSettings.TILE_NEUTRAL; this.logManager.addNewLogMessage(GlobalSettings.LOG_NEUTRAL_ON); } else { String noMapMsg = GlobalSettings.LOG_WARNING + GlobalSettings.LOG_GENERATE_OPEN_MAP_FIRST; this.logManager.addNewLogMessage(noMapMsg); } } /** * The event handler when Canvas is clicked. * * @param event The MouseEvent object */ @FXML private void canvasOnClick(MouseEvent event) { // Check first if the mouseclick event is inside the grip map if (this.graphicsManager.isInsideTheGrid(event.getX(), event.getY())) { // Extract tile position from mouseclick coordinates int[] tilePos = this.graphicsManager.getTilePosition( event.getX(), event.getY()); // Check if any tile button (boulder/neutral/player) is activated if (this.isEditMapOn) { boolean isBoulder = false; boolean isPlayer1Start = false; boolean isPlayer2Start = false; Integer[] boulderReference = null; // Check current selected tile if it is a boulder tile for (Integer[] boulder : this.listOfBoulders) { if (tilePos[0] == boulder[0] && tilePos[1] == boulder[1]) { isBoulder = true; boulderReference = boulder; } } // Check current selected tile if it is a player 1 start tile if (this.map.getListOfPlayer1StartPosition() != null) { if (tilePos[0] == this.map.getListOfPlayer1StartPosition()[0] && tilePos[1] == this.map.getListOfPlayer1StartPosition()[1]) { isPlayer1Start = true; } } // Check current selected tile if it is a player 2 start tile if (this.map.getListOfPlayer2StartPosition() != null) { if (tilePos[0] == this.map.getListOfPlayer2StartPosition()[0] && tilePos[1] == this.map.getListOfPlayer2StartPosition()[1]) { isPlayer2Start = true; } } // Apply necessary action to the tile selected switch (this.tileToEdit) { case GlobalSettings.TILE_BOULDER: // If the tile selected is not a boulder, change it to boulder if (!isBoulder) { // Check if it is previously a player 1 start tile if (isPlayer1Start) { // Remove player 1 start reference this.map.setListOfPlayer1StartPosition(null); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + "Player 1 start position is overwritten!"); } // Check if it is previously a player 2 start tile if (isPlayer2Start) { // Remove player 2 start reference this.map.setListOfPlayer2StartPosition(null); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + "Player 2 start position is overwritten!"); } // Add tile to list of boulders this.listOfBoulders.add(new Integer[]{tilePos[0], tilePos[1]}); // Paint tile to boulder this.graphicsManager.paintBoulderTile(tilePos[0], tilePos[1]); // Toggle flag for detecting unsave map this.isCurrentMapSave = false; String msgBoulderSet = GlobalSettings.LOG_TILE_SET + "Boulder tile is set to " + "[" + tilePos[0] + "," + tilePos[1] + "]"; this.logManager.addNewLogMessage(msgBoulderSet); } else { this.logManager.addNewLogMessage( GlobalSettings.LOG_NOTE + "It's already a boulder tile."); } break; case GlobalSettings.TILE_NEUTRAL: if (isBoulder) { // Remove from boulder list this.listOfBoulders.remove(boulderReference); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + GlobalSettings.LOG_BOULDER_OVERWRITTEN); } // Check if it is previously a player 1 start tile if (isPlayer1Start) { // Remove player 1 start reference this.map.setListOfPlayer1StartPosition(null); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + "Player 1 start position is overwritten!"); } // Check if it is previously a player 2 start tile if (isPlayer2Start) { // Remove player 2 start reference this.map.setListOfPlayer2StartPosition(null); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + "Player 2 start position is overwritten!"); } // This is necessary to avoid overpainting previous neutral tile if (isBoulder || isPlayer1Start || isPlayer2Start) { // Paint tile to neutral this.graphicsManager.paintNeutralTile(tilePos[0], tilePos[1]); // Toggle flag for detecting unsave map this.isCurrentMapSave = false; String msgNeutralSet = GlobalSettings.LOG_TILE_SET + "Neutral tile is set to " + "[" + tilePos[0] + "," + tilePos[1] + "]"; this.logManager.addNewLogMessage(msgNeutralSet); } else { this.logManager.addNewLogMessage( GlobalSettings.LOG_NOTE + "It's already a neutral tile."); } break; case GlobalSettings.TILE_PLAYER1: if (isBoulder) { // Remove from boulder list this.listOfBoulders.remove(boulderReference); // Revert it to neutral tile this.graphicsManager.paintNeutralTile(tilePos[0], tilePos[1]); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + GlobalSettings.LOG_BOULDER_OVERWRITTEN); } // If there is a previous player 1 start tile if (this.map.getListOfPlayer1StartPosition() != null) { // Check if the selected tile is on the same start position // No need to repaint it again if (tilePos[0] == this.map.getListOfPlayer1StartPosition()[0] && tilePos[1] == this.map.getListOfPlayer1StartPosition()[1]) { this.logManager.addNewLogMessage( GlobalSettings.LOG_ERROR + "You already have selected this tile."); // Reset isEditOn this.isEditMapOn = false; // Break early break; } else { // If not, remove previous start tile // and repaint it to neutral this.graphicsManager.paintNeutralTile( this.map.getListOfPlayer1StartPosition()[0], this.map.getListOfPlayer1StartPosition()[1]); // Just in case, to be safe, set it back to null this.map.setListOfPlayer1StartPosition(null); } } // Check if it is previously a player 2 start tile // If this is already a player 2 position, it resets the // player 2 start position to null. Null because it's easy to // detect later on if it is properly configure before saving the map if (isPlayer2Start) { // Remove player 2 start reference this.map.setListOfPlayer2StartPosition(null); // Revert it to neutral tile this.graphicsManager.paintNeutralTile(tilePos[0], tilePos[1]); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + "Player 2 start position is overwritten!"); } // Paint tile to player 1 start this.graphicsManager.paintPlayerStart(1, tilePos[0], tilePos[1]); // Toggle flag for detecting unsave map this.isCurrentMapSave = false; // Add start position to map this.map.setListOfPlayer1StartPosition(tilePos.clone()); // LOG Message String msgPlayer1Log; if (isPlayer2Start) { msgPlayer1Log = GlobalSettings.LOG_TILE_OVERWRITTEN; } else { msgPlayer1Log = GlobalSettings.LOG_TILE_SET; } msgPlayer1Log += "Player 1 start position is now set to " + "[" + tilePos[0] + "," + tilePos[1] + "]"; this.logManager.addNewLogMessage(msgPlayer1Log); // Reset isEditOn this.isEditMapOn = false; break; case GlobalSettings.TILE_PLAYER2: if (isBoulder) { // Remove from boulder list this.listOfBoulders.remove(boulderReference); // Revert it to neutral tile this.graphicsManager.paintNeutralTile(tilePos[0], tilePos[1]); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + GlobalSettings.LOG_BOULDER_OVERWRITTEN); } // If there is a previous player 2 start tile if (this.map.getListOfPlayer2StartPosition() != null) { // Check if the selected tile is on the same start position // No need to repaint it again if (tilePos[0] == this.map.getListOfPlayer2StartPosition()[0] && tilePos[1] == this.map.getListOfPlayer2StartPosition()[1]) { this.logManager.addNewLogMessage( GlobalSettings.LOG_ERROR + "You already have selected this tile."); // Reset isEditOn this.isEditMapOn = false; // Break early break; } else { // If not, remove previous start tile // and repaint it to neutral this.graphicsManager.paintNeutralTile( this.map.getListOfPlayer2StartPosition()[0], this.map.getListOfPlayer2StartPosition()[1]); // Just in case, to be safe, set it back to null this.map.setListOfPlayer2StartPosition(null); } } // Check if it is previously a player 1 start tile // If this is already a player 1 position, it resets the // player 1 start position to null. Null because it's easy to // detect later on if it is properly configure before saving the map if (isPlayer1Start) { // Remove player 2 start reference this.map.setListOfPlayer1StartPosition(null); // Revert it to neutral tile this.graphicsManager.paintNeutralTile(tilePos[0], tilePos[1]); this.logManager.addNewLogMessage( GlobalSettings.LOG_WARNING + "Player 1 start position is overwritten!"); } // Paint tile to player 2 start this.graphicsManager.paintPlayerStart(2, tilePos[0], tilePos[1]); // Toggle flag for detecting unsave map this.isCurrentMapSave = false; // Add start position to map this.map.setListOfPlayer2StartPosition(tilePos.clone()); // LOG Message String msgPlayer2Log; if (isPlayer1Start) { msgPlayer2Log = GlobalSettings.LOG_TILE_OVERWRITTEN; } else { msgPlayer2Log = GlobalSettings.LOG_TILE_SET; } msgPlayer2Log += "Player 2 start position is now set to " + "[" + tilePos[0] + "," + tilePos[1] + "]"; this.logManager.addNewLogMessage(msgPlayer2Log); // Reset isEditOn this.isEditMapOn = false; break; } // Update map list boulder data this.map.setListOfBoulders(listOfBoulders); } else { String msgNoObjectSelected = GlobalSettings.LOG_NOTE + "Nothing selected. " + "[" + tilePos[0] + "," + tilePos[1] + "]"; this.logManager.addNewLogMessage(msgNoObjectSelected); } } else { String msg = GlobalSettings.LOG_WARNING + "Mouse clicked " + "[" + event.getX() + "," + event.getY() + "] " + "is outside the grid map!"; this.logManager.addNewLogMessage(msg); } } /** * The event handler when Title field is on focus. * * @param event The KeyEvent object */ @FXML private void titleFieldOnKeyPressed(KeyEvent event) { // Detect if key pressed is Enter key if (event.getCode() == KeyCode.ENTER) { // If a map is generated either by opening a file or new map, // save map title to current map object if (this.isThereAMapVisible) { String title = this.title_field.getText(); if (title.isEmpty()) { String warningMsg1 = GlobalSettings.LOG_WARNING + "Title field is empty!"; this.logManager.addNewLogMessage(warningMsg1); // Better be safe than sorry return; } else { // Only accepts letters, numbers and spaces // No leading space allowed // All trailing spaces are automatically trim if (title.matches("^[a-zA-Z0-9][a-zA-Z0-9\\s]*$")) { title = this.titleFormatter(title); // Set title to the map this.map.setName(title); // Toggle flag for detecting unsave map this.isCurrentMapSave = false; String successMsg = GlobalSettings.LOG_TITLE_SET + "Title: " + title + " is set."; this.logManager.addNewLogMessage(successMsg); } else { String msgHead = "Invalid Title"; String msgBody = "Only use alphanumeric and space characters. No leading space."; String invalidMsg = GlobalSettings.LOG_ERROR + msgHead + ". " + msgBody; this.logManager.addNewLogMessage(invalidMsg); Dialog dialog = new Dialog( DialogType.ERROR, msgHead, msgBody); dialog.showAndWait(); } } } else { String noMapMsg = GlobalSettings.LOG_WARNING + GlobalSettings.LOG_GENERATE_OPEN_MAP_FIRST; this.logManager.addNewLogMessage(noMapMsg); } } } /** * Formats the title of the map. * * @param title The map's title * @return Returns the formatted title */ private String titleFormatter(String title) { // Capitilize all first letter of a word // separated by space title = title.toLowerCase(); title = StringUtils.capitalizeFirstLetterEachWord(title); // Update title field this.title_field.setText(title); // append row and column title += " " + this.numberOfColumns + "x" + this.numberOfRows; return title; } /** * The event handler when Menu>Open menu item is clicked. * * @param event The ActionEvent object */ @FXML private void menuFileOpenOnClick(ActionEvent event) { FileChooser fileChooser = new FileChooser(); // Set filechooser title fileChooser.setTitle("Open Map File (.json)"); // Set filechooser initial directory to "home" depending on user OS fileChooser.setInitialDirectory( new File(System.getProperty("user.home")) ); // Add json extension filter FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("JSON Map files (*.json)", "*.json"); fileChooser.getExtensionFilters().add(extFilter); // Launch filechooser and get open file File file = fileChooser.showOpenDialog(this.app.getStage()); if (this.verifyFileToOpen(file)) { this.openMap(file); } } public boolean verifyFileToOpen(File file) { return MapManager.verifyFileToOpen(file); } /** * Opens the map json file and set it to the map instance variable. * * @param file The map json file * @return <code>true</code> if a file is successfully opened, otherwise, * <code>false</code>. */ public boolean openMap(File file) { Map tempMapHolder = MapManager.openFile(file, this.isCurrentMapSave); if (tempMapHolder != null) { this.map = tempMapHolder; } else { return false; } // Toggle flag that the map is an open map file this.isOpeningAMap = true; // Extract columns and rows this.numberOfColumns = this.map.getSize()[0]; this.numberOfRows = this.map.getSize()[1]; // Set comboboxes this.column_combo.setValue(this.numberOfColumns); this.row_combo.setValue(this.numberOfRows); // Set title String rawTitle = this.map.getName(); String title = rawTitle.substring(0, rawTitle.lastIndexOf(" ")); this.title_field.setText(title); // Set list of boulders this.listOfBoulders = this.map.getListOfBoulders(); // Generate map this.generateMap(file.getPath()); // Set to current file map // Use to compare for recent map menu item this.currentFileOpened = file; // Add to list of current opened files if (!this.listOfRecentFiles.contains(file)) { this.listOfRecentFiles.add(file); } // Update recent files menu items and add listeners if (!this.listOfRecentFiles.isEmpty()) { // Resets menu items this.menuFileRecent.getItems().clear(); for (File aFile : this.listOfRecentFiles) { // Create new menu item MenuItem aMenuItem = new MenuItem(aFile.getPath()); // Add menu item to the recent open menu this.menuFileRecent.getItems().add(aMenuItem); // Set handler aMenuItem.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { // Get back the reference from the menuitem object MenuItem anObject = (MenuItem) event.getSource(); // only opens a map if it is not "open" currently if (!currentFileOpened.getPath().equals(anObject.getText())) { openMap(new File(anObject.getText())); } else { String itsAlreadyOpenLogMessage = GlobalSettings.LOG_NOTE + "Map is already open."; logManager.addNewLogMessage(itsAlreadyOpenLogMessage); } } }); } } return true; } @FXML private void menuFileRecentOnClick(ActionEvent event) { } public class RecentMapEventHandler implements EventHandler<ActionEvent> { private File openedFile; public RecentMapEventHandler(File file) { this.openedFile = file; } @Override public void handle(ActionEvent event) { System.out.println("it's clicked"); openMap(openedFile); } } /** * The event handler when File>Save menu item is clicked. * * @param event The ActionEvent object */ @FXML private void menuFileSaveOnClick(ActionEvent event) { // Detect first if all requirements are met if (this.map == null) { String emptyMapMsg = GlobalSettings.LOG_ERROR + "No map opened or generated."; this.logManager.addNewLogMessage(emptyMapMsg); Dialog warningDialog = new Dialog( DialogType.WARNING, GlobalSettings.DIALOG_WARNING_SAVE_HEAD_MSG, emptyMapMsg); warningDialog.showAndWait(); return; } if (this.map.getListOfPlayer1StartPosition() == null) { String emptyP1StartMsg = GlobalSettings.LOG_ERROR + "Player 1 start position is not set!"; this.logManager.addNewLogMessage(emptyP1StartMsg); Dialog warningDialog = new Dialog( DialogType.WARNING, GlobalSettings.DIALOG_WARNING_SAVE_HEAD_MSG, emptyP1StartMsg); warningDialog.showAndWait(); return; } if (this.map.getListOfPlayer2StartPosition() == null) { String emptyP2StartMsg = GlobalSettings.LOG_ERROR + "Player 2 start position is not set!"; this.logManager.addNewLogMessage(emptyP2StartMsg); Dialog warningDialog = new Dialog( DialogType.WARNING, GlobalSettings.DIALOG_WARNING_SAVE_HEAD_MSG, emptyP2StartMsg); warningDialog.showAndWait(); return; } if (this.map.getName() == null) { String emptyTitleMsg = GlobalSettings.LOG_ERROR + "Map title is not set!"; this.logManager.addNewLogMessage(emptyTitleMsg); Dialog warningDialog = new Dialog( DialogType.WARNING, GlobalSettings.DIALOG_WARNING_SAVE_HEAD_MSG, emptyTitleMsg); warningDialog.showAndWait(); return; } else if (this.map.getName().isEmpty()) { String emptyTitleMsg = GlobalSettings.LOG_ERROR + "Map title is not set!"; this.logManager.addNewLogMessage(emptyTitleMsg); Dialog warningDialog = new Dialog( DialogType.WARNING, GlobalSettings.DIALOG_WARNING_SAVE_HEAD_MSG, emptyTitleMsg); warningDialog.showAndWait(); return; } // Create a new filchooser for saving file FileChooser fileChooser = new FileChooser(); // Set filechooser title fileChooser.setTitle("Save Map File"); // Set filechooser initial directory to "home" depending on user OS fileChooser.setInitialDirectory( new File(System.getProperty("user.home")) ); // Add json extension filter FileChooser.ExtensionFilter extFilter = new FileChooser.ExtensionFilter("JSON Map files (*.json)", "*.json"); fileChooser.getExtensionFilters().add(extFilter); // Launch filechooser and get open file File file = fileChooser.showSaveDialog(this.app.getStage()); if (verifyFileToSave(file)) { this.saveMap(file); } } public boolean verifyFileToSave(File file) { return MapManager.verifyFileToSave(file, this.logManager, this.map, this.numberOfColumns, this.numberOfRows); } public boolean saveMap(File file) { this.isCurrentMapSave = MapManager.saveFile(file, this.map, this.canvas); return this.isCurrentMapSave; } /** * The event handler when File>New menu item is clicked. * * @param event The ActionEvent object */ @FXML private void menuFileNewOnClick(ActionEvent event) { this.generateBtnOnClick(event); } /** * Gets a reference to the MainApp object, once ViewController is initiated. * <p> * This also setups an eventHandler for handling on close window button * click to launch a confirmation dialog * * @param app The main MainApp object initiated */ public void setApp(MainApp app) { this.app = app; // For detecting user pressing close window button // for exit dialog confirmation process this.app.getStage().setOnCloseRequest(new EventHandler<WindowEvent>() { @Override public void handle(WindowEvent event) { String msgHead, msgBody; if (isCurrentMapSave) { msgHead = GlobalSettings.DIALOG_QUIT_HEAD_MSG; msgBody = GlobalSettings.DIALOG_QUIT_BODY_MSG; } else { msgHead = GlobalSettings.DIALOG_QUIT_HEAD_MSG_NOT_SAVE; msgBody = GlobalSettings.DIALOG_QUIT_BODY_MSG_NOT_SAVE; } Dialog confirmDialog = new Dialog( DialogType.CONFIRMATION, DialogText.CONFIRMATION_HEADER.getText(), msgHead, msgBody); confirmDialog.showAndWait(); if (confirmDialog.getResponse() == DialogResponse.NO || confirmDialog.getResponse() == DialogResponse.CLOSE) { // event consume halts any further action (shutting down) // WARNING: Do not mess with else condition with // event.consume() or user won't be able to close the app!! // If somehow you toyed with this idea and can't close // it via window close button, you can alternatively use // File quit menu item and don't forget to remove your stuff. event.consume(); } } }); } /** * The event handler when File>Quit menu item is clicked. * * @param event The ActionEvent object */ @FXML private void menuFileQuitOnClick(ActionEvent event) { String msgHead, msgBody; if (this.isCurrentMapSave) { msgHead = GlobalSettings.DIALOG_QUIT_HEAD_MSG; msgBody = GlobalSettings.DIALOG_QUIT_BODY_MSG; } else { msgHead = GlobalSettings.DIALOG_QUIT_HEAD_MSG_NOT_SAVE; msgBody = GlobalSettings.DIALOG_QUIT_BODY_MSG_NOT_SAVE; } Dialog confirmDialog = new Dialog( DialogType.CONFIRMATION, DialogText.CONFIRMATION_HEADER.getText(), msgHead, msgBody); confirmDialog.showAndWait(); if (confirmDialog.getResponse() == DialogResponse.YES) { // This is a preferred exit solution for any JavaFX application // instead of System.exit(0) Platform.exit(); } } /** * The event handler when Log>Clear menu item is clicked. * * @param event The ActionEvent object */ @FXML private void meuLogClearLogOnClick(ActionEvent event) { this.logManager.reset(); } /** * The event handler when About>About App menu item is clicked. Opens a * dialog window about the application. * * @param event The ActionEvent object */ @FXML private void menuAboutOnClick(ActionEvent event) { AboutView aboutView = new AboutView(); aboutView.showAndWait(); } /** * @return Returns <tt>true</tt> if a map is generated on the canvas, * otherwise false */ public boolean isThereAMapVisible() { return isThereAMapVisible; } /** * @return Returns true if any of player or object buttons is clicked */ public boolean isEditMapOn() { return isEditMapOn; } }