/** * BetonQuest Editor - advanced quest creating tool for BetonQuest * Copyright (C) 2016 Jakub "Co0sh" Sapalski * * 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 pl.betoncraft.betonquest.editor; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.ResourceBundle; import java.util.stream.Collectors; import javafx.application.Application; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Alert; import javafx.scene.control.Alert.AlertType; import javafx.scene.control.ButtonType; import javafx.scene.image.Image; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.BorderPane; import javafx.stage.Modality; import javafx.stage.Stage; import pl.betoncraft.betonquest.editor.controller.ConversationController; import pl.betoncraft.betonquest.editor.controller.EcoController; import pl.betoncraft.betonquest.editor.controller.ExceptionController; import pl.betoncraft.betonquest.editor.controller.MainController; import pl.betoncraft.betonquest.editor.controller.MainMenuController; import pl.betoncraft.betonquest.editor.controller.OtherController; import pl.betoncraft.betonquest.editor.controller.RootController; import pl.betoncraft.betonquest.editor.controller.TabsController; import pl.betoncraft.betonquest.editor.controller.TranslationController; import pl.betoncraft.betonquest.editor.model.Condition; import pl.betoncraft.betonquest.editor.model.Conversation; import pl.betoncraft.betonquest.editor.model.Event; import pl.betoncraft.betonquest.editor.model.Item; import pl.betoncraft.betonquest.editor.model.JournalEntry; import pl.betoncraft.betonquest.editor.model.Objective; import pl.betoncraft.betonquest.editor.model.PackageSet; import pl.betoncraft.betonquest.editor.model.PackageSet.SaveType; import pl.betoncraft.betonquest.editor.model.PointCategory; import pl.betoncraft.betonquest.editor.model.QuestPackage; import pl.betoncraft.betonquest.editor.model.Tag; import pl.betoncraft.betonquest.editor.persistence.Persistence; import pl.betoncraft.betonquest.editor.persistence.PreviousState; /** * Main class of the application. * * @author Jakub Sapalski */ public class BetonQuestEditor extends Application { private static final String VERSION = "0.1.2"; private static BetonQuestEditor instance; private Stage stage; private ResourceBundle language; private List<PackageSet> loadedSets = new LinkedList<>(); private QuestPackage currentPackage; public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) { try { instance = this; new Persistence(); stage = primaryStage; createPrimaryStage(); loadPreviousData(); displayPreviousData(); } catch (Exception e) { ExceptionController.display(e); } } private void createPrimaryStage() throws IOException { URL location = getClass().getResource("view/Root.fxml"); Locale locale = Persistence.getSettings().getLanguage(); language = ResourceBundle.getBundle("pl.betoncraft.betonquest.editor.resource.lang.lang", locale); FXMLLoader fxmlLoader = new FXMLLoader(location, language); BorderPane root = (BorderPane) fxmlLoader.load(); TabsController.setDisabled(true); Scene scene = new Scene(root, 1280, 720); scene.getStylesheets().add(getClass().getResource("resource/style.css").toExternalForm()); KeyCombination save = new KeyCodeCombination(KeyCode.S, KeyCombination.CONTROL_DOWN); KeyCombination export = new KeyCodeCombination(KeyCode.E, KeyCombination.CONTROL_DOWN); scene.addEventFilter(KeyEvent.KEY_PRESSED, event -> { if (save.match(event)) { event.consume(); MainMenuController.getInstance().save(); } else if (export.match(event)) { event.consume(); MainMenuController.getInstance().export(); } }); stage.setScene(scene); stage.setTitle(language.getString("betonquest-editor")); stage.getIcons().add(new Image(getClass().getResourceAsStream("resource/icon.png"))); stage.setMinHeight(600); stage.setMinWidth(800); stage.setMaximized(true); stage.show(); } private void loadPreviousData() throws IOException { PreviousState prev = Persistence.getPreviousState(); if (!prev.getOpenPackageSets().isEmpty()) { for (String packageAddress : prev.getOpenPackageSets()) { File file = new File(packageAddress); if (!file.exists()) { continue; } if (packageAddress.endsWith(".zip")) { PackageSet.loadFromZip(file); } else { PackageSet.loadFromDirectory(file); } } } } private void displayPreviousData() { PreviousState prev = Persistence.getPreviousState(); String setName = prev.getSelectedPackageSet(); Optional<PackageSet> set = getSets().stream() .filter(s -> s.getName().get().equals(setName)).findFirst(); set.ifPresent(s -> { display(s); String packName = prev.getSelectedPackage(); Optional<QuestPackage> pack = s.getPackages().stream().filter( p -> p.getName().get().equals(packName)).findFirst(); pack.ifPresent(p -> { display(p); }); int tabIndex = prev.getSelectedTab(); TabsController.selectTab(tabIndex); }); } /** * @return the instance of the editor */ public static BetonQuestEditor getInstance() { return instance; } /** * Reloads the view. * * @throws IOException */ public static void reload() throws IOException { // cache previous window size double width = instance.stage.getWidth(); double height = instance.stage.getHeight(); // reload the view instance.saveState(); instance.createPrimaryStage(); instance.displayPreviousData(); // restore window size instance.stage.setWidth(width); instance.stage.setHeight(height); } /** * @return version of the editor */ public String getVesion() { return VERSION; } /** * @return the primary stage of this application */ public Stage getPrimaryStage() { return stage; } /** * @return ResourceBundle containing language strings */ public ResourceBundle getLanguage() { return language; } /** * @return the list of loaded packages */ public List<PackageSet> getSets() { return loadedSets; } /** * Returns PackageSet with specified name or null, if it does not exist. */ public PackageSet getSet(String setName) { for (PackageSet set : loadedSets) { if (set.getName().get().equals(setName)) { return set; } } return null; } /** * @return the package currently displayed in the view */ public QuestPackage getDisplayedPackage() { return currentPackage; } /** * Displays the first package in this set in the view. */ public void display(PackageSet set) { QuestPackage pack = set.getPackages().get(0); display(pack); } /** * Displays a package in the view. */ public void display(QuestPackage pack) { clearView(); currentPackage = pack; currentPackage.sort(); RootController.setPackageSets(loadedSets); MainController.setCompassTarget(pack.getCompassTargets()); MainController.setGlobVariables(pack.getVariables()); MainController.setStaticEvents(pack.getStaticEvents()); MainController.setGlobalLocations(pack.getLocations()); MainController.setQuestCancelers(pack.getCancelers()); MainController.setMainPageLines(pack.getMainPage()); MainMenuController.setSaveAsEnabled(true); MainMenuController.setExportEnabled(true); MainMenuController.setSaveEnabled(pack.getSet().getFile() != null && pack.getSet().getSaveType() != SaveType.NONE); MainMenuController.setPackEnabled(true); ConversationController.setConversations(pack.getConversations()); EcoController.setConditions(pack.getConditions()); EcoController.setEvents(pack.getEvents()); EcoController.setObjectives(pack.getObjectives()); OtherController.setItems(pack.getItems()); OtherController.setJournal(pack.getJournal()); TranslationController.setLanguages(pack.getTranslationManager()); TabsController.setDisabled(false); } /** * Clears the view. Should be called before displaying different package, * but not before refreshing. That way current conversation, option etc. are * kept. */ public void clearView() { currentPackage = null; RootController.clear(); MainController.clear(); MainMenuController.setSaveAsEnabled(false); MainMenuController.setSaveEnabled(false); MainMenuController.setExportEnabled(false); MainMenuController.setPackEnabled(false); ConversationController.clearConversation(); EcoController.clear(); OtherController.clear(); TranslationController.clear(); TabsController.setDisabled(true); } /** * Refreshes the currently displayed package */ public void refresh() { if (currentPackage != null) { display(currentPackage); } RootController.setPackageSets(loadedSets); } /** * @return all conditions from loaded packages, the ones from current package first */ public ObservableList<Condition> getAllConditions() { ObservableList<Condition> list = FXCollections.observableArrayList(); list.addAll(currentPackage.getConditions()); currentPackage.getSet().getPackages().forEach(pack -> { if (!pack.equals(currentPackage)) { list.addAll(pack.getConditions()); } }); return list; } /** * @return all events from loaded packages, the ones from current package first */ public ObservableList<Event> getAllEvents() { ObservableList<Event> list = FXCollections.observableArrayList(); list.addAll(currentPackage.getEvents()); currentPackage.getSet().getPackages().forEach(pack -> { if (!pack.equals(currentPackage)) { list.addAll(pack.getEvents()); } }); return list; } /** * @return all objectives from loaded packages, the ones from current package first */ public ObservableList<Objective> getAllObjectives() { ObservableList<Objective> list = FXCollections.observableArrayList(); list.addAll(currentPackage.getObjectives()); currentPackage.getSet().getPackages().forEach(pack -> { if (!pack.equals(currentPackage)) { list.addAll(pack.getObjectives()); } }); return list; } /** * @return all conversations from loaded packages, the ones from current package first */ public ObservableList<Conversation> getAllConversations() { ObservableList<Conversation> list = FXCollections.observableArrayList(); list.addAll(currentPackage.getConversations()); currentPackage.getSet().getPackages().forEach(pack -> { if (!pack.equals(currentPackage)) { list.addAll(pack.getConversations()); } }); return list; } /** * @return all items from loaded packages, the ones from current package first */ public ObservableList<Item> getAllItems() { ObservableList<Item> list = FXCollections.observableArrayList(); list.addAll(currentPackage.getItems()); currentPackage.getSet().getPackages().forEach(pack -> { if (!pack.equals(currentPackage)) { list.addAll(pack.getItems()); } }); return list; } /** * @return all journal entries from loaded packages, the ones from current package first */ public ObservableList<JournalEntry> getAllEntries() { ObservableList<JournalEntry> list = FXCollections.observableArrayList(); list.addAll(currentPackage.getJournal()); currentPackage.getSet().getPackages().forEach(pack -> { if (!pack.equals(currentPackage)) { list.addAll(pack.getJournal()); } }); return list; } /** * @return all tags from loaded packages, the ones from current package first */ public ObservableList<Tag> getAllTags() { ObservableList<Tag> list = FXCollections.observableArrayList(); list.addAll(currentPackage.getTags()); currentPackage.getSet().getPackages().forEach(pack -> { if (!pack.equals(currentPackage)) { list.addAll(pack.getTags()); } }); return list; } /** * @return all point categories from loaded packages, the ones from current package first */ public ObservableList<PointCategory> getAllPoints() { ObservableList<PointCategory> list = FXCollections.observableArrayList(); list.addAll(currentPackage.getPoints()); currentPackage.getSet().getPackages().forEach(pack -> { if (!pack.equals(currentPackage)) { list.addAll(pack.getPoints()); } }); return list; } /** * Shows an error pop-up window with specified translated message. * * @param message ID of message */ public static void showError(String message) { Alert alert = new Alert(AlertType.ERROR); alert.setHeaderText(BetonQuestEditor.getInstance().getLanguage().getString(message)); alert.getDialogPane().getStylesheets().add(instance.getClass().getResource("resource/style.css").toExternalForm()); alert.showAndWait(); } /** * Shows a confirmation pop-up window with specified translated message. * Returns result - true if the user confirms the action, false if he wants * to cancel it. * * @param message ID of message * @return true if confirmed, false otherwise */ public static boolean confirm(String message) { Alert alert = new Alert(AlertType.CONFIRMATION); alert.setHeaderText(BetonQuestEditor.getInstance().getLanguage().getString(message)); alert.getDialogPane().getStylesheets().add(instance.getClass().getResource("resource/style.css").toExternalForm()); Optional<ButtonType> action = alert.showAndWait(); if (action.isPresent() && action.get() == ButtonType.OK) { return true; } return false; } /** * Creates a pop-up window with specified parameters and returns the controller object. * * @param fxml path of the FXML resource file * @param title ID of translated window title message * @param width width of the window * @param height height of the window * @return the controller oject for this window */ public static Object createWindow(String fxml, String title, int width, int height) { try { Stage window = new Stage(); URL location = BetonQuestEditor.class.getResource(fxml); ResourceBundle resources = ResourceBundle.getBundle("pl.betoncraft.betonquest.editor.resource.lang.lang", Persistence.getSettings().getLanguage()); FXMLLoader fxmlLoader = new FXMLLoader(location, resources); Parent root = (Parent) fxmlLoader.load(); Scene scene = new Scene(root); scene.getStylesheets().add(BetonQuestEditor.class.getResource("resource/style.css").toExternalForm()); window.setScene(scene); window.setTitle(resources.getString(title)); window.getIcons().add(new Image(BetonQuestEditor.class.getResourceAsStream("resource/icon.png"))); window.setHeight(height); window.setWidth(width); window.setResizable(false); window.initModality(Modality.WINDOW_MODAL); window.initOwner(instance.stage); return fxmlLoader.getController(); } catch (IOException e) { ExceptionController.display(e); return null; } } @Override public void stop() throws Exception { saveState(); super.stop(); } private void saveState() throws IOException { if (!getSets().isEmpty()) { // save current state to a file using PreviousState PreviousState prev = Persistence.getPreviousState(); // prepare current state List<String> list = getSets().stream().map(s -> s.getFile().getAbsolutePath()).collect(Collectors.toList()); String set = getDisplayedPackage().getSet().getName().get(); String pack = getDisplayedPackage().getName().get(); int tab = TabsController.getSelectedTab(); // set current state in PreviousState prev.setOpenPackageSets(list); prev.setSelectedPackageSet(set); prev.setSelectedPackage(pack); prev.setSelectedTab(tab); // save it to the file prev.save(); } } }