ca.wumbo.doommanager.client.controller.FileEditorController.java Source code

Java tutorial

Introduction

Here is the source code for ca.wumbo.doommanager.client.controller.FileEditorController.java

Source

/*
 * DoomManager
 * Copyright (C) 2014  Chris K
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package ca.wumbo.doommanager.client.controller;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import ca.wumbo.doommanager.client.controller.file.ConnectWizardController;
import ca.wumbo.doommanager.client.controller.file.DXMLProjectCreatorController;
import ca.wumbo.doommanager.client.controller.file.TaskManagerController;
import ca.wumbo.doommanager.client.file.ClientDoomFileManagerTab;
import ca.wumbo.doommanager.client.util.Controllable;
import ca.wumbo.doommanager.client.util.Resources;
import ca.wumbo.doommanager.client.util.SelfInjectableController;
import ca.wumbo.doommanager.file.dxml.DXML;
import ca.wumbo.doommanager.file.dxml.DXMLCreationStatus;
import ca.wumbo.doommanager.file.dxml.DXMLException;
import ca.wumbo.doommanager.file.entry.designator.PK3EntryDesignator;
import ca.wumbo.doommanager.file.entry.designator.WadEntryDesignator;
import ca.wumbo.doommanager.file.entry.types.PK3Entry;
import ca.wumbo.doommanager.file.entry.types.WadEntry;
import ca.wumbo.doommanager.util.SpringContainer;
import ca.wumbo.doommanager.util.Tickable;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuBar;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TabPane;
import javafx.scene.control.TabPane.TabClosingPolicy;
import javafx.scene.control.ToolBar;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Modality;
import javafx.stage.Stage;

/**
 * The controller for the file editor, which is responsible for editing files
 * and creating/saving/modifying Wads, PK's... etc.
 */
public class FileEditorController extends SelfInjectableController implements Controllable, Tickable {

    /**
     * The logger for this class.
     */
    private static final Logger log = LogManager.getLogger(FileEditorController.class);

    @Autowired
    private CoreController coreController;

    @Autowired
    private TaskManagerController taskManagerController;

    @Autowired
    private Resources resources;

    @Value("${fileeditor.controller.fxmlpath}")
    private String fxmlPath;

    @Value("${css.default.path}")
    private String cssPath;

    //=========================================================================

    @FXML
    private BorderPane rootBorderPane;

    @FXML
    private VBox topVBox;

    @FXML
    private MenuBar menuBar;

    @FXML
    private Menu mainMenu;

    @FXML
    private Menu fileMenu;

    @FXML
    private Menu newMenu;

    @FXML
    private MenuItem newDXMLProjectMenuItem;

    @FXML
    private MenuItem newPK3MenuItem;

    @FXML
    private MenuItem newWadMenuItem;

    @FXML
    private MenuItem openFileMenuItem;

    @FXML
    private MenuItem connectFileMenuItem;

    @FXML
    private MenuItem exitMenuItem;

    @FXML
    private ToolBar toolbar;

    @FXML
    private Button newWadButton;

    @FXML
    private Button openFileButton;

    @FXML
    private Button connectButton;

    @FXML
    private Button taskManagerButton;

    @FXML
    private TabPane fileTabPane;

    //=========================================================================

    /**
     * Only to be instantiated by Spring.
     */
    private FileEditorController() {
    }

    @FXML
    private void initialize() {
        // We only want the user to close the selected tab, not accidentally other tabs.
        fileTabPane.setTabClosingPolicy(TabClosingPolicy.SELECTED_TAB); // TODO - this should be configurable though as a user option.

        // We'll remove the text since its not needed (only useful in the SceneBuilder).
        for (Node node : toolbar.getItems()) {
            if (node instanceof Button) {
                Button button = (Button) node;
                button.setText("");
            }
        }

        // Set menu item graphics.
        newMenu.setGraphic(new ImageView(resources.getImage("newfile")));
        newWadMenuItem.setGraphic(new ImageView(resources.getImage("wadentry")));
        newPK3MenuItem.setGraphic(new ImageView(resources.getImage("pk3entry")));
        newDXMLProjectMenuItem.setGraphic(new ImageView(resources.getImage("dxmlprojectentry")));
        openFileMenuItem.setGraphic(new ImageView(resources.getImage("folder")));
        connectFileMenuItem.setGraphic(new ImageView(resources.getImage("network")));
        exitMenuItem.setGraphic(new ImageView(resources.getImage("close")));

        // Set button graphics.
        newWadButton.setGraphic(new ImageView(resources.getImage("newfile")));
        openFileButton.setGraphic(new ImageView(resources.getImage("folder")));
        connectButton.setGraphic(new ImageView(resources.getImage("network")));
        taskManagerButton.setGraphic(new ImageView(resources.getImage("taskmanager")));
    }

    /**
     * Loads the FXML data and injects it into this object. Should be called by
     * Spring right after the constructor and dependencies are linked. To 
     * reduce code duplication, this functionality was moved to a containing
     * class.
     * 
     * @throws NullPointerException
     *       If the FXML path is null.
     * 
     * @throws RuntimeException
     *       If the FXML file is missing or corrupt.
     */
    @PostConstruct
    public void loadFXML() {
        super.loadFXML(fxmlPath);
    }

    /**
     * Creates a new DXML project and has the user set it up, then places it in
     * the tab.
     */
    public void newDXMLProject() {
        // Create the wizard and wait for the result.
        DXMLProjectCreatorController controller = SpringContainer.getContext()
                .getBean(DXMLProjectCreatorController.class);

        Scene scene = new Scene(controller.getRootPane());
        scene.getStylesheets().add(cssPath);

        Stage stage = new Stage();
        stage.setTitle("DXML Project Setup Wizard");
        stage.setResizable(false);
        stage.initModality(Modality.WINDOW_MODAL);
        stage.initOwner(coreController.getStage());
        stage.setScene(scene);
        controller.setStage(stage); // Required for it to be able to close properly.
        controller.setParentStage(coreController.getStage()); // Required as well to block input if browsing for files.
        stage.showAndWait();

        // If the user finished the work, get the DXML file.
        if (!controller.wasClosedByUser()) {
            DXML dxmlData = controller.getDxmlData();
            // The file must also be valid. This should not ever be false unless there is a coding error.
            if (dxmlData.isValidDXMLFile()) {
                // Create it on the disk.
                DXMLCreationStatus status = dxmlData.createOnDisk();

                // If the status shows success, add it to the view.
                switch (status) {
                case SUCCESS:
                    // Since it succeeded, open up the project now.
                    String path = dxmlData.getProjectInfo().getProjectLocationPath() + File.separator;
                    path += dxmlData.getCompilation().getFilename() + File.separator;
                    path += dxmlData.getCompilation().getFilename() + ".dxml"; // TODO - support any case extension.
                    openDXMLProject(Paths.get(path));
                    break;
                case ALREADY_EXISTS:
                    log.warn("DXML project already exists at the specified location. Aborting.");
                    break;
                case FOLDER_CREATION_FAILURE:
                    log.warn("Unable to create directory for the project. Are your permissions correct? Aborting.");
                    break;
                case DXML_FILE_EXISTS:
                    log.warn("DXML file already exists at location, aborting to prevent potential overwrite.");
                    break;
                case DXML_FILE_CREATION_FAILURE:
                    log.warn("Unable to create DXML file at location, are your permissions correct? Aborting.");
                    break;
                case XML_MARSHALL_ERROR:
                    log.warn("Unable to create the .dxml file. Are your file permissions correct? Aborting.");
                    break;
                case FAILED:
                    log.warn("Unable to create the necessary DXML file/folder resources at the provided location.");
                    break;
                case INVALID_DXML_DATA:
                case BAD_PROJECT_PATH:
                    log.warn("DXML project has corrupt data. Contact a developer, Aborting [Reason: {}].",
                            status.name());
                    break;
                default:
                    throw new DXMLException(
                            "DXML status for disk creation returned an unexpected enumeration. Contact a developer.");
                }
            } else {
                throw new DXMLException(
                        "DXML Wizard has not properly generated a proper file. Please contact a developer.");
            }
        } else {
            log.info("DXML project cancelled by user.");
        }
    }

    /**
     * Creates a new PK3 and places it in the tab.
     */
    public void newPK3() {
        log.info("Creating new empty PK3.");
        PK3Entry pk3Entry = new PK3Entry("NEWFILE.pk3");
        ClientDoomFileManagerTab doomFileTab = new ClientDoomFileManagerTab(pk3Entry);
        fileTabPane.getTabs().add(doomFileTab);
    }

    /**
     * Creates a new wad and places it in the tab.
     */
    public void newWad() {
        log.info("Creating new empty Wad.");
        WadEntry wadEntry = new WadEntry("NEWFILE.wad", false);
        ClientDoomFileManagerTab doomFileTab = new ClientDoomFileManagerTab(wadEntry);
        fileTabPane.getTabs().add(doomFileTab);
    }

    /**
     * Requests the user to select a file to open.
     */
    public void openFile() {
        log.debug("Attempting to open file...");

        // Provide a file chooser that looks for extensions we know.
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Open file");
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("Any Doom File", "*.wad", "*.WAD", "*.pk3", "*.PK3", "*.dxml",
                        "*.DXML"),
                new FileChooser.ExtensionFilter("DoomManager Project Files", "*.dxml", "*.DXML"),
                new FileChooser.ExtensionFilter("Wad Files", "*.wad", "*.WAD"),
                new FileChooser.ExtensionFilter("PK3 Files", "*.pk3", "*.PK3"),
                new FileChooser.ExtensionFilter("All files", "*.*"));
        List<File> files = fileChooser.showOpenMultipleDialog(coreController.getStage());

        // If the user selected one or more files...
        if (files != null) {
            // For each file the user selected...
            for (File file : files) {
                Path filePath = file.toPath();
                String lowerPath = filePath.toString().toLowerCase();
                log.debug("Attempting to load file from {}...", filePath.toString());

                // Handle based on ending:
                if (lowerPath.endsWith(".wad")) {
                    openWad(filePath);
                } else if (lowerPath.endsWith(".pk3")) {
                    openPK3(filePath);
                } else if (lowerPath.endsWith(".dxml")) {
                    openDXMLProject(filePath);
                }
            }
        }
    }

    /**
     * Attempts to open a wad at the file path.
     * 
     * @param filePath
     *       The file path to open the wad at.
     */
    private void openWad(Path filePath) {
        assert filePath != null : "Passed a null file path to openWad.";
        try {
            taskManagerController.processEntryDesignation(new WadEntryDesignator(fileTabPane, filePath));
        } catch (FileNotFoundException e) {
            log.warn("Could not find file at {}.", filePath.toString());
        }
    }

    /**
     * Attempts to open a pk3 at the file path.
     * 
     * @param filePath
     *       The file path to open the pk3 at.
     */
    private void openPK3(Path filePath) {
        assert filePath != null : "Passed a null file path to openPK3.";
        try {
            taskManagerController.processEntryDesignation(new PK3EntryDesignator(fileTabPane, filePath));
        } catch (FileNotFoundException e) {
            log.warn("Could not find file at {}.", filePath.toString());
        }
    }

    /**
     * Attempts to open a DXML project at the file path.
     * 
     * @param filePath
     *       The file path to open the project at.
     */
    private void openDXMLProject(Path filePath) {
        //      try {
        //         DXML dxml = new DXML(filePath.toFile());
        //         
        //         // Only continue on if it's a valid file (if it's corrupt, we might not know where to get data).
        //         if (dxml.isValidDXMLFile()) {
        //            // Create the source folder from the project.
        //            Entry projectEntryTreeItem = Designator.designateDXML(filePath.toFile(), dxml);
        //            
        //            // Add it to the tabs.
        //            DoomFileManagerTab doomFileTab = new DoomFileManagerTab(projectEntryTreeItem, filePath);
        //            fileTabPane.getTabs().add(doomFileTab);
        //            
        //            log.info("Loaded DXML file from {}", filePath.toString());
        //         } else {
        //            log.warn("DXML file (at {}) is corrupt. Please check the DXML GUI menu for corrupt elements.", filePath.toString());
        //         }
        //      } catch (DXMLException e) {
        //         log.warn("Unable to properly read DXML file at {}.", filePath.toString());
        //         log.debug(e);
        //      } catch (IOException e) {
        //         log.warn("Error reading files/folders in the source directory for the DXML file at {}.", filePath.toString());
        //         log.debug(e);
        //      }
    }

    /**
     * Starts the GUI for connecting to a server for a shared project.
     */
    public void openConnectGUI() {
        log.debug("Loading a new connect wizard.");
        ConnectWizardController connectWizard = SpringContainer.getContext().getBean(ConnectWizardController.class);

        log.trace("Creating the scene.");
        Scene scene = new Scene(connectWizard.getRootPane());
        scene.getStylesheets().add(cssPath);

        log.trace("Creating stage to display window with the scene.");
        Stage stage = new Stage();
        stage.setTitle("Remote Project Connection");
        stage.setResizable(false);
        stage.initModality(Modality.NONE);
        stage.initOwner(coreController.getStage());
        stage.setResizable(false);
        stage.setScene(scene);
        stage.show();
        log.trace("Connect wizard displayed.");
    }

    /**
     * Displays the task manager window.
     */
    public void showTaskManager() {
        taskManagerController.showWindow();
    }

    /**
     * Called when the client selects the "close" option.
     */
    public void requestExit() {
        coreController.exit();
    }

    @Override
    public Pane getRootPane() {
        return rootBorderPane;
    }

    @Override
    public void tick() {
        // TODO
    }
}