Package org.solarus.editor.gui

Source Code of org.solarus.editor.gui.EditorWindow

/*
* Copyright (C) 2006-2014 Christopho, Solarus - http://www.solarus-games.org
*
* Solarus Quest Editor 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.
*
* Solarus Quest Editor 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 org.solarus.editor.gui;

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.nio.file.NoSuchFileException;
import java.util.*;

import javax.swing.*;
import javax.swing.event.*;

import org.solarus.editor.*;
import org.solarus.editor.Map;
import org.solarus.editor.gui.tree.QuestTree;

/**
* Main window of the editor.
*/
public class EditorWindow extends JFrame
    implements Observer, ProjectObserver, ChangeListener {

    private EditorTabs tabs;
    private QuestTree questTree;

    // Menus and menu items memorized to enable or disable them later.
    private JMenu menuFile;
    private JMenu menuNew;
    private JMenuItem menuNewMap;
    private JMenuItem menuNewTileset;
    private JMenu menuOpen;
    private JMenuItem menuOpenMap;
    private JMenuItem menuOpenTileset;
    private JMenuItem menuItemSave;
    private JMenuItem menuItemClose;
    private JMenuItem menuItemUndo;
    private JMenuItem menuItemRedo;
    private JMenuItem menuItemCut;
    private JMenuItem menuItemCopy;
    private JMenuItem menuItemPaste;

    /**
     * Creates a new window.
     * @param questPath Path of a quest to load,
     * or null to load the default quest if any.
     */
    public EditorWindow(String questPath) {
        super("Solarus Quest Editor " + Project.solarusFormat);

        // set a nice look and feel
        GuiTools.setLookAndFeel();

        tabs = new EditorTabs();
        tabs.addChangeListener(this);

        questTree = new QuestTree(this);
        questTree.setVisible(true);
        final JScrollPane questTreeScroller = new JScrollPane(questTree);
        questTreeScroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        questTreeScroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        final JSplitPane mainSplitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, questTreeScroller, tabs);
        mainSplitter.setOneTouchExpandable(true);
        mainSplitter.setDividerLocation(300);

        getContentPane().add(mainSplitter, BorderLayout.CENTER);

        // add a window listener to confirm when the user closes the window
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                if (checkCurrentFilesSaved()) {
                    System.exit(0);
                }
            }
        });

        // create the menu bar
        createMenuBar();
        update(null, null);
        if (questPath != null) {
            loadProject(questPath);
        }

        // This object must be added as project observer after the quest tree.
        // This is because we want the quest tree to be up-to-date before we
        // build or destroy subtrees of maps.
        Project.addProjectObserver(this);
    }

    /**
     * Returns the quest tree displayed in the left part of the window.
     * @return The quest tree.
     */
    public QuestTree getQuestTree() {
        return questTree;
    }

    /**
     * This function is called when the history changes.
     * @param o the history
     * @param obj additional parameter
     */
    public void update(Observable o, Object obj) {

        menuFile.setEnabled(Project.isLoaded());

        if (tabs.getSelectedComponent() != null) {

            menuItemUndo.setEnabled(false);
            menuItemRedo.setEnabled(false);
            menuItemCut.setEnabled(false);
            menuItemCopy.setEnabled(false);
            menuItemPaste.setEnabled(false);

            if (tabs.getSelectedComponent() instanceof MapEditorPanel) {

                MapEditorPanel mapEditor = (MapEditorPanel) tabs.getSelectedComponent();
                MapEditorHistory history = mapEditor.getMap().getHistory();
                menuItemUndo.setEnabled(history.canUndo());
                menuItemRedo.setEnabled(history.canRedo());

                boolean emptySelection = mapEditor.getMap().getEntitySelection().isEmpty();
                menuItemCut.setEnabled(!emptySelection);
                menuItemCopy.setEnabled(!emptySelection);
                menuItemPaste.setEnabled(mapEditor.getMapView().canPaste());
            }
        }
    }

    /**
     * Buils the menu bar of the window.
     */
    public void createMenuBar() {
        JMenuBar menuBar = new JMenuBar();

        JMenu menu;
        JMenuItem item;

        // menu Project
        menu = new JMenu("Quest");
        menu.setMnemonic(KeyEvent.VK_Q);

        item = new JMenuItem("New quest...");
        item.setMnemonic(KeyEvent.VK_N);
        item.getAccessibleContext().setAccessibleDescription("Create a new Solarus quest");
        item.addActionListener(new ActionListenerNewProject());
        menu.add(item);

        item = new JMenuItem("Load quest...");
        item.setMnemonic(KeyEvent.VK_O);
        item.getAccessibleContext().setAccessibleDescription("Open an existing Solarus quest");
        item.addActionListener(new ActionListenerLoadProject());
        menu.add(item);

        menu.addSeparator();

        item = new JMenuItem("Quit");
        item.setMnemonic(KeyEvent.VK_Q);
        item.getAccessibleContext().setAccessibleDescription("Exit Solarus Quest Editor");
        item.addActionListener(new ActionListenerQuit());
        menu.add(item);

        menuBar.add(menu);

        // Menu File
        menuFile = new JMenu("File");
        menuFile.setEnabled(false);
        menuFile.setMnemonic(KeyEvent.VK_F);
        // Menu File > New
        menuNew = new JMenu("New");
        menuNew.setMnemonic(KeyEvent.VK_N);
        // Item File > New > Map
        menuNewMap = new JMenuItem("Map");
        menuNewMap.setMnemonic(KeyEvent.VK_M);
        menuNewMap.getAccessibleContext().setAccessibleDescription("Create a new map");
        menuNewMap.addActionListener(
                new ActionListenerCreateResourceElement(ResourceType.MAP));
        menuNew.add(menuNewMap);

        // Item File > New > Tileset
        menuNewTileset = new JMenuItem("Tileset");
        menuNewTileset.setMnemonic(KeyEvent.VK_T);
        menuNewTileset.getAccessibleContext().setAccessibleDescription("Create a new tileset");
        menuNewTileset.addActionListener(
                new ActionListenerCreateResourceElement(ResourceType.TILESET));
        menuNew.add(menuNewTileset);
        menuFile.add(menuNew);

        // Menu File > Open
        menuOpen = new JMenu("Open");
        menuOpen.setMnemonic(KeyEvent.VK_O);

        menuOpenMap = new JMenuItem("Map");
        menuOpenMap.setMnemonic(KeyEvent.VK_M);
        menuOpenMap.addActionListener(
                new ActionListenerOpenResourceElement(ResourceType.MAP));
        menuOpen.add(menuOpenMap);

        menuOpenTileset = new JMenuItem("Tileset");
        menuOpenTileset.setMnemonic(KeyEvent.VK_T);
        menuOpenTileset.getAccessibleContext().setAccessibleDescription("Open an existing tileset");
        menuOpenTileset.addActionListener(
                new ActionListenerOpenResourceElement(ResourceType.TILESET));
        menuOpen.add(menuOpenTileset);

        menuFile.add(menuOpen);

        menuItemClose = new JMenuItem("Close");
        menuItemClose.setMnemonic(KeyEvent.VK_C);
        menuItemClose.getAccessibleContext().setAccessibleDescription("Close the current editor");
        menuItemClose.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK));
        menuItemClose.addActionListener(new ActionListenerCloseCurrentEditor());
        menuFile.add(menuItemClose);

        menuItemSave = new JMenuItem("Save");
        menuItemSave.setMnemonic(KeyEvent.VK_S);
        menuItemSave.getAccessibleContext().setAccessibleDescription("Save the current editor");
        menuItemSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
        menuItemSave.addActionListener(new ActionListenerSaveCurrentEditor());
        menuFile.add(menuItemSave);

        menuFile.addSeparator();

        menuItemUndo = new JMenuItem("Undo");
        menuItemUndo.setMnemonic(KeyEvent.VK_U);
        menuItemUndo.getAccessibleContext().setAccessibleDescription("Undo the last action");
        menuItemUndo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
        menuItemUndo.addActionListener(new ActionListenerUndoMap());
        menuItemUndo.setEnabled(false);
        menuFile.add(menuItemUndo);

        menuItemRedo = new JMenuItem("Redo");
        menuItemRedo.setMnemonic(KeyEvent.VK_R);
        menuItemRedo.getAccessibleContext().setAccessibleDescription("Redo the last action undone");
        menuItemRedo.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, ActionEvent.CTRL_MASK));
        menuItemRedo.addActionListener(new ActionListenerRedoMap());
        menuItemRedo.setEnabled(false);
        menuFile.add(menuItemRedo);

        menuFile.addSeparator();

        menuItemCut = new JMenuItem("Cut");
        menuItemCut.setMnemonic(KeyEvent.VK_U);
        menuItemCut.getAccessibleContext().setAccessibleDescription("Cut the selected entities");
        menuItemCut.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
        menuItemCut.addActionListener(new ActionListenerCutMap());
        menuItemCut.setEnabled(false);
        menuFile.add(menuItemCut);

        menuItemCopy = new JMenuItem("Copy");
        menuItemCopy.setMnemonic(KeyEvent.VK_C);
        menuItemCopy.getAccessibleContext().setAccessibleDescription("Copy the selected entities");
        menuItemCopy.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
        menuItemCopy.addActionListener(new ActionListenerCopyMap());
        menuItemCopy.setEnabled(false);
        menuFile.add(menuItemCopy);

        menuItemPaste = new JMenuItem("Paste");
        menuItemPaste.setMnemonic(KeyEvent.VK_P);
        menuItemPaste.getAccessibleContext().setAccessibleDescription("Paste the copied entities");
        menuItemPaste.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK));
        menuItemPaste.addActionListener(new ActionListenerPasteMap());
        menuItemPaste.setEnabled(false);
        menuFile.add(menuItemPaste);

        menuBar.add(menuFile);

        setJMenuBar(menuBar);
    }

    /**
     * Sets the title of the window.
     */
    private void updateWindowTitle() {
        setTitle(Project.getPathBaseName() + " - Solarus Quest Editor "
                + Project.solarusFormat);
    }

    /**
     * This function is called when the user wants to quit the editor
     * If any resource open is not saved, we propose to save it.
     * @return false if the user canceled.
     */
    public boolean checkCurrentFilesSaved() {
        boolean result = true;
        if (tabs.countEditors() > 0) {
            for (AbstractEditorPanel editor : tabs.getEditors()) {
                result = result && editor.checkCurrentFileSaved();
            }
        }
        return result;
    }

    /**
     * This method is called just after a project is loaded.
     */
    public void currentProjectChanged() {
        menuFile.setEnabled(true);
        tabs.removeAll();
        updateWindowTitle();
    }

    /**
     * Prompts the user for a directory and creates a new project
     * in that directory.
     */
    private void newProject() {
        if (!checkCurrentFilesSaved()) {
            return;
        }

        ProjectFileChooser chooser = new ProjectFileChooser();
        String projectPath = chooser.getProjectPath();

        if (projectPath != null) {
            try {
                Project.createNew(projectPath);
                GuiTools.informationDialog("Quest successfully created!\n" +
                    "The next step is to manually edit your quest properties in quest.dat\n" +
                    "(sorry, this is not fully supported by the editor yet).\n");
            }
            catch (QuestEditorException ex) {
                GuiTools.errorDialog("Cannot create the project: " + ex.getMessage());
            }
        }
    }

    /**
     * Prompts the user for a directory and loads the project
     * located in that directory.
     */
    private void loadProject() {

        if (!checkCurrentFilesSaved()) {
            return;
        }

        ProjectFileChooser chooser = new ProjectFileChooser();
        String questPath = chooser.getProjectPath();

        if (questPath != null) {
            loadProject(questPath);
        }
    }

    /**
     * Loads the project located in the specified directory.
     * @param questPath Path of the quest to load.
     */
    private void loadProject(String questPath) {

        if (!checkCurrentFilesSaved()) {
            return;
        }

        try {
            tabs.removeAll();
            Project.createExisting(questPath);
        }
        catch (ObsoleteQuestException ex) {
            // Quest data files are obsolete: upgrade them and try again.
            boolean upgrade = GuiTools.okCancelDialog(
                "The format of this quest (" + ex.getQuestFormat()
                + ") is outdated.\n"
                + "Your data files will be automatically updated to Solarus "
                + Project.solarusFormat + ".");
            if (upgrade && upgradeProject(questPath, ex.getQuestFormat())) {
                loadProject(questPath);
            }
        }
        catch (ObsoleteEditorException ex) {
            GuiTools.errorDialog(ex.getMessage());
        }
        catch (QuestEditorException ex) {
            GuiTools.errorDialog("Cannot load the project: " + ex.getMessage());
        }
    }

    /**
     * Upgrades the data files of the specified project to the most recent
     * format.
     * A dialog box is shown to display the status of the operation.
     * @param questPath Path of the quest to upgrade.
     * @param questFormat The (obsolete) format of the quest.
     * @return true in case of success.
     */
    private boolean upgradeProject(String questPath, String questFormat) {

        try {
            // First backup the files.
            String backupDirectory = questPath + "/data." + questFormat + ".bak";
            FileTools.deleteRecursive(backupDirectory)// Remove any previous backup.
            FileTools.copyDirectory(questPath + "/data", backupDirectory);

            // Upgrade data files.
            ExternalLuaScriptDialog dialog = new ExternalLuaScriptDialog("Upgrading quest data files",
                "update_quest", questPath);
            boolean upgradeSuccess = dialog.display();
            if (!upgradeSuccess) {
                // The upgrade failed.
                try {
                    // Restore the backuped version.
                    FileTools.deleteRecursive(questPath + "/data.err");
                    FileTools.renameDirectory(questPath + "/data", questPath + "/data.err");
                    FileTools.deleteRecursive(questPath + "/data");
                    FileTools.renameDirectory(backupDirectory, questPath + "/data");
                    GuiTools.warningDialog("Sorry, an error occured while upgrading the quest.\n"
                            + "Your quest was kept unchanged in format " + questFormat + ".");
                    return false;
                }
                catch (IOException ex) {
                    // The restoration failed for some reason.
                    GuiTools.warningDialog("Sorry, an error occured while upgrading the quest.\n"
                            + "A backup of your quest was saved in format " + questFormat
                            + " was saved in '" + backupDirectory + "'.");
                    return false;
                }
            }
        }
        catch (NoSuchFileException ex) {
            ex.printStackTrace();
            GuiTools.errorDialog("Failed to backup the quest data files:\nNo such file: " + ex.getMessage());
            return false;
        }
        catch (IOException ex) {
            GuiTools.errorDialog("Failed to backup the quest data files:\n" + ex.getMessage());
            return false;
        }
        return true;
    }

    /**
     * Action performed when the user clicks on Project > New project.
     * Creates a new project, asking to the user the project path.
     */
    private class ActionListenerNewProject implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            newProject();
        }
    }

    /**
     * Action performed when the user clicks on Project > Load project.
     * Loads an existing project, asking to the user the project path.
     */
    private class ActionListenerLoadProject implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            loadProject();
        }
    }

    /**
     * Action performed when the user wants to exit the program.
     */
    public class ActionListenerQuit implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            if (checkCurrentFilesSaved()) {
                System.exit(0);
            }
        }
    }

    /**
     * Action performed when the user wants to create a new resource element.
     */
    public class ActionListenerCreateResourceElement implements ActionListener {

        private ResourceType resourceType;

        public ActionListenerCreateResourceElement(ResourceType resourceType) {
            this.resourceType = resourceType;
        }

        @Override
        public void actionPerformed(ActionEvent ev) {
            createResourceElement(resourceType, "");
        }
    }

    /**
     * Action performed when the user wants to open a resource element.
     */
    public class ActionListenerOpenResourceElement implements ActionListener {

        private ResourceType resourceType;

        public ActionListenerOpenResourceElement(ResourceType resourceType) {
            this.resourceType = resourceType;
        }

        @Override
        public void actionPerformed(ActionEvent ev) {
            openResourceElement(resourceType);
        }
    }

    /**
     * Action performed when the user clicks on File > Close.
     * Closes the current editor unless the user is not okay with that.
     */
    private class ActionListenerCloseCurrentEditor implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            tabs.removeCurrentEditor(true);
        }
    }

    /**
     * Action performed when the user clicks on File > Save.
     * Saves the resource in this editor.
     */
    private class ActionListenerSaveCurrentEditor implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            tabs.saveCurrentEditor();
        }
    }

    /**
     * Action performed when user the user clicks on Edit > Undo or presses Ctrl + Z.
     * The last action (if any) on the map is canceled.
     */
    private class ActionListenerUndoMap implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            MapEditorPanel mapEditor = (MapEditorPanel) tabs.getSelectedComponent();
            try {
                mapEditor.getMap().getHistory().undo();
            } catch (QuestEditorException ex) {
                GuiTools.errorDialog("Cannot undo: " + ex.getMessage());
            }
        }
    }

    /**
     * Action performed when user the user clicks on Edit > Redo or presses Ctrl + Y.
     * The last action canceled (if any) is done again.
     */
    private class ActionListenerRedoMap implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            MapEditorPanel mapEditor = (MapEditorPanel) tabs.getSelectedComponent();
            try {
                mapEditor.getMap().getHistory().redo();
            } catch (QuestEditorException ex) {
                GuiTools.errorDialog("Cannot redo: " + ex.getMessage());
            }
        }
    }

    /**
     * Action performed when user the user clicks on Edit > Cut or presses Ctrl + X.
     * The selected entities are cut.
     */
    private class ActionListenerCutMap implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            MapEditorPanel mapEditor = (MapEditorPanel) tabs.getSelectedComponent();
            mapEditor.getMapView().cutSelectedEntities();
        }
    }

    /**
     * Action performed when user the user clicks on Edit > Copy or presses Ctrl + C.
     * The selected entities are copied.
     */
    private class ActionListenerCopyMap implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            MapEditorPanel mapEditor = (MapEditorPanel) tabs.getSelectedComponent();
            mapEditor.getMapView().copySelectedEntities();
        }
    }

    /**
     * Action performed when user the user clicks on Edit > Paste or presses Ctrl + V.
     * The copied entities are added to the map.
     */
    private class ActionListenerPasteMap implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent ev) {
            MapEditorPanel mapEditor = (MapEditorPanel) tabs.getSelectedComponent();
            mapEditor.getMapView().paste();
        }
    }

    /**
     * Called when the state of the tabs has changed.
     */
    @Override
    public void stateChanged(ChangeEvent e) {
        update(null, null);
    }

    /**
     * Creates a resource element, asking its id to the user.
     * @param resourceType Type of resource element to create.
     * @param basepath the default path of the resource.
     */
    public void createResourceElement(ResourceType resourceType, String basepath) {

        String resourceName = resourceType.getName();
        try {
            ResourceBuilderDialog dialog = new ResourceBuilderDialog(resourceType, basepath);
            if (dialog.display()) {
                String id = dialog.getId();
                String friendlyName = dialog.getFriendlyName();

                if (Project.getResource(resourceType).exists(id)) {
                    throw new QuestEditorException(
                            resourceName + " '" + id + "' already exists");
                }
                Project.newResourceElement(resourceType, id, friendlyName);
            }
        } catch (QuestEditorException ex) {
            GuiTools.errorDialog("Cannot create " + resourceName + ": " + ex.getMessage());
        }
    }

    /**
     * Creates a new lua script, asking its name to the user.
     * @param path The default path of the new script.
     * @return the file name (relative to the project data directory) or null if no exists
     */
    public String createNewLuaScript(String path) {

        NewLuaScriptDialog dialog = new NewLuaScriptDialog(path.isEmpty() ? "" : path + "/");
        if (!dialog.display()) {
            return null;
        }

        String name = dialog.getFileName();
        File file = new File(Project.getDataPath() + "/" + name);

        try {
            if (file.exists()) {
                throw new QuestEditorException("this file already exists");
            }
            if (!Project.ensureParentDirectoryExists(file)) {
                throw new QuestEditorException(
                        "the parent directory no exists and cannot be create");
            }

            file.createNewFile();
            return name;
        }
        catch (Exception ex) {
            GuiTools.errorDialog("Cannot create the script '" +
                    file.getAbsolutePath() + "': " + ex.getMessage());
        }
        return null;
    }

    /**
     * Creates a new directory, asking its name to the user.
     * @param path The default path of the new directory.
     * @return the directory name (relative to the project data directory) or null if no exists
     */
    public String createNewDirectory(String path) {

        NewDirectoryDialog dialog = new NewDirectoryDialog(path.isEmpty() ? "" : path + "/");
        if (!dialog.display()) {
            return null;
        }

        String name = dialog.getDirName();
        File file = new File(Project.getDataPath() + "/" + name);

        try {
            if (file.exists()) {
                throw new QuestEditorException("this directory already exists");
            }

            file.mkdirs();
            return name;
        }
        catch (Exception ex) {
            GuiTools.errorDialog("Cannot create the directory '" +
                    file.getAbsolutePath() + "': " + ex.getMessage());
        }
        return null;
    }

    /**
     * Opens a resource element, asking its id to the user.
     * @param resourceType Type of resource.
     */
    public void openResourceElement(ResourceType resourceType) {

        String resourceName = resourceType.getName();
        String resourceNameLower = resourceName.toLowerCase();

        ResourceChooserDialog dialog = new ResourceChooserDialog(resourceType);
        dialog.setLocationRelativeTo(EditorWindow.this);
        dialog.pack();
        dialog.setVisible(true);
        String id = dialog.getSelectedId();

        if (id.isEmpty()) {
            return;
        }

        if (!Project.getResource(resourceType).exists(id)) {
            GuiTools.errorDialog(
                    "No such " + resourceNameLower + ": '" + id + "'");
        }
        openResourceElement(resourceType, id);
    }

    /**
     * Ensures that a resource element file exists.
     * If the file doesn't exists, ask and try to create it.
     * @param resourceFile the file of the resource element
     * @param resourceType the type of the resource element
     * @param resourceId the id of the resource element
     * @return true if the file exists or has been successfully created, false otherwise
     * @throws QuestEditorException If the file doesn't exist and cannot be created
     */
    private boolean ensureResourceElementFileExists(File resourceFile,
            ResourceType resourceType, String resourceId) throws QuestEditorException {

        if (!resourceFile.exists()) {
            String resourceTypeName = resourceType.getName().toLowerCase();
            if (!GuiTools.yesNoDialog("The " + resourceTypeName  +
                    " '" + resourceId + "' doesn't exists yet.\n" +
                    "Do you want to create this " + resourceTypeName + "?")) {
                return false;
            }
            try {
                resourceFile.createNewFile();
            } catch (IOException ex) {
                throw new QuestEditorException("the file '" + resourceFile.getAbsolutePath() +
                        "' cannot be created: " + ex.getMessage());
            }
        }
        return true;
    }

    /**
     * Opens a resource element with its default editor.
     * @param resourceType Type of resource.
     * @param resourceId Id of the new element.
     */
    public void openResourceElement(ResourceType resourceType, String resourceId) {

        try {
            AbstractEditorPanel existingEditor;
            switch (resourceType) {

            case MAP:
            {
                existingEditor = tabs.getEditor(MapEditorPanel.getEditorId(resourceId));
                if (existingEditor != null) {
                    tabs.setSelectedComponent(existingEditor);
                }
                else {

                    File file = Project.getMapFile(resourceId);
                    if (ensureResourceElementFileExists(file, resourceType, resourceId)) {

                        MapEditorPanel mapEditor = new MapEditorPanel(this, resourceId);
                        tabs.addEditor(mapEditor);
                        // Keep menu items Undo, Cut, Copy, etc. synchronized.
                        mapEditor.getMap().addObserver(this);
                    }
                }
                break;
            }

            case TILESET:
            {
                existingEditor = tabs.getEditor(TilesetEditorPanel.getEditorId(resourceId));
                if (existingEditor != null) {
                    tabs.setSelectedComponent(existingEditor);
                }
                else {

                    File file = Project.getTilesetFile(resourceId);
                    if (ensureResourceElementFileExists(file, resourceType, resourceId)) {

                        TilesetEditorPanel tilesetEditor = new TilesetEditorPanel(this, resourceId);
                        tabs.addEditor(tilesetEditor);
                    }
                }
                break;
            }

            case LANGUAGE:
            {
                openTextEditor(Project.getStringsFile(resourceId));
                break;
            }

            case ITEM:
            {
                openTextEditor(Project.getItemScriptFile(resourceId));
                break;
            }

            case SPRITE:
            {
                existingEditor = tabs.getEditor(SpriteEditorPanel.getEditorId(resourceId));
                if (existingEditor != null) {
                    tabs.setSelectedComponent(existingEditor);
                }
                else {

                    File file = Project.getSpriteFile(resourceId);
                    if (ensureResourceElementFileExists(file, resourceType, resourceId)) {

                        SpriteEditorPanel spriteEditor = new SpriteEditorPanel(this, resourceId);
                        tabs.addEditor(spriteEditor);
                    }
                }
                break;
            }

            case ENEMY:
            {
                openTextEditor(Project.getEnemyScriptFile(resourceId));
                break;
            }

            case ENTITY:
            {
                openTextEditor(Project.getEntityScriptFile(resourceId));
                break;
            }

            case SOUND:
            case MUSIC:
            case FONT:
                // No editor for these kinds of resources.
                break;
            }
        }
        catch (QuestEditorException ex) {
            GuiTools.errorDialog(
                    "Cannot open " + resourceType.getName().toLowerCase()
                    + ": " + ex.getMessage());
        }
        catch (NoClassDefFoundError ex) {
            ex.printStackTrace();
            if (ex.getMessage().contains("lua")) {
                // The LuaJ jar is probably missing in the classpath.
                GuiTools.errorDialog(
                        "Cannot open " + resourceType.getName().toLowerCase()
                        + ": The LuaJ jar dependency is missing.\n"
                        + "Please make sure that luaj-jse-2.0.2.jar exists and "
                        + "it is placed in the current directory, and then "
                        + "restart the editor.");
            }
            else {
                // Unknown problem.
                GuiTools.errorDialog(
                        "Cannot open " + resourceType.getName().toLowerCase()
                        + ": " + ex.toString());
            }
        }
        catch (Throwable ex) {
            // Unknown problem.
            ex.printStackTrace();
            GuiTools.errorDialog(
                    "Cannot open " + resourceType.getName().toLowerCase()
                    + ": " + ex.toString());
        }
    }

    /**
     * Opens a file in a text editor.
     * @param file File to open.
     */
    public void openTextEditor(File file) {

        AbstractEditorPanel existingEditor =
                tabs.getEditor(TextEditorPanel.getEditorId(file));
        if (existingEditor != null) {
            tabs.setSelectedComponent(existingEditor);
        }
        else {
            TextEditorPanel textEditor = new TextEditorPanel(this, file);
            tabs.addEditor(textEditor);
        }
    }

    /**
     * Closes the editors of a resource element if some are open.
     * Lets the use save the editors if he wants.
     * @param resourceType Type of resource element to close.
     * @param elementId Id of the resource element to close.
     * @param promptSave true to let the user save the element if necessary,
     * false to close it without confirmation.
     */
    public void closeResourceElement(ResourceType resourceType,
            String elementId, boolean promptSave) {

        // Determine ids of editors to remove.
        ArrayList<String> editorIds = new ArrayList<String>();

        switch (resourceType) {

        case MAP:
        {
            editorIds.add(MapEditorPanel.getEditorId(elementId));
            editorIds.add(TextEditorPanel.getEditorId(Project.getMapScriptFile(elementId)));
            break;
        }

        case TILESET:
        {
            editorIds.add(TilesetEditorPanel.getEditorId(elementId));
            break;
        }

        case LANGUAGE:
        {
            editorIds.add(TextEditorPanel.getEditorId(Project.getDialogsFile(elementId)));
            editorIds.add(TextEditorPanel.getEditorId(Project.getStringsFile(elementId)));
            break;
        }

        case ENEMY:
        {
            editorIds.add(TextEditorPanel.getEditorId(Project.getEnemyScriptFile(elementId)));
            break;
        }

        case ITEM:
        {
            editorIds.add(TextEditorPanel.getEditorId(Project.getItemScriptFile(elementId)));
            break;
        }

        case ENTITY:
        {
            editorIds.add(TextEditorPanel.getEditorId(Project.getEntityScriptFile(elementId)));
            break;
        }

        case SPRITE:
        {
            editorIds.add(SpriteEditorPanel.getEditorId(elementId));
            break;
        }

        case SOUND:
        case MUSIC:
        case FONT:
            // No editor for these kinds of resources.
            break;
        }

        for (String editorId: editorIds) {
            tabs.removeEditor(editorId, promptSave);
        }
    }

    /**
     * Closes the text editor of a file.
     * @param file File to close.
     * @param promptSave true to let the user save the element if necessary,
     * false to close it without confirmation.
     */
    public void closeTextEditor(File file, boolean promptSave) {

        String id = TextEditorPanel.getEditorId(file);
        tabs.removeEditor(id, promptSave);
    }

    /**
     * Changes the id of a resource element, asking the new id to the user.
     * @param resourceType Type of resource element to change.
     * @param oldId The id to change.
     */
    public void moveResourceElement(ResourceType resourceType, String oldId) {

        // First, close the element if it is open.
        closeResourceElement(resourceType, oldId, true);

        // Ask the new id.
        String resourceName = resourceType.getName();
        String resourceNameLower = resourceName.toLowerCase();
        try {
            String newId = (String) JOptionPane.showInputDialog(
                    null,
                    "Please enter a new id for " + resourceNameLower + " '" + oldId + "'",
                    "Change id of " + resourceNameLower + " '" + oldId + "'",
                    JOptionPane.QUESTION_MESSAGE, null, null, oldId);

            if (newId != null) {
                if (Project.getResource(resourceType).exists(newId)) {
                    throw new QuestEditorException(
                            resourceName + " '" + newId + "' already exists");
                }
                Project.moveElement(resourceType, oldId, newId);
            }
        } catch (QuestEditorException ex) {
            GuiTools.errorDialog("Cannot change id of " + resourceName + " '"
                    + oldId + "': " + ex.getMessage());
        }
    }

    /**
     * Changes the human-readable name of a resource element, asking the new
     * name to the user.
     * @param resourceType Type of resource element to change.
     * @param id Id of the element to change.
     */
    public void renameResourceElement(ResourceType resourceType, String id) {

        // Ask the new name.
        Resource resource = Project.getResource(resourceType);
        String resourceName = resourceType.getName();
        String resourceNameLower = resourceName.toLowerCase();
        try {
            String oldName = resource.getElementName(id);
            String newName = (String) JOptionPane.showInputDialog(
                    null,
                    "Please enter a new name for '" + oldName + "'",
                    "Rename " + resourceNameLower + " '" + oldName + "'",
                    JOptionPane.QUESTION_MESSAGE, null, null, oldName);

            if (newName != null) {
                Project.renameResourceElement(resourceType, id, newName);
            }
        } catch (QuestEditorException ex) {
            GuiTools.errorDialog("Cannot rename " + resourceName + " '"
                    + id + "': " + ex.getMessage());
        }
    }

    /**
     * Deletes a resource element after confirmation from the user.
     * @param resourceType Type of resource.
     * @param resourceId Id of the element to delete.
     */
    public void deleteResourceElement(ResourceType resourceType, String resourceId) {
        try {
            int answer = JOptionPane.showConfirmDialog(this,
                    "Are you sure you want to delete "
                            + resourceType.getName() + " '" + resourceId + "'?",
                    "Are you sure?",
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.WARNING_MESSAGE);
            if (answer == JOptionPane.YES_OPTION) {
                // Close the element if it is open.
                closeResourceElement(resourceType, resourceId, false);

                // Delete it.
                Project.deleteResourceElement(resourceType, resourceId);
            }
        }
        catch (QuestEditorException ex) {
            GuiTools.errorDialog("Could not delete " + resourceType.getName()
                    + ": " + ex.getMessage());
        }
    }

    /**
     * Called when a new resource element has just been created.
     * @param resourceType Type of resource.
     * @param id Id of the new element.
     */
    @Override
    public void resourceElementAdded(ResourceType resourceType, String id) {
        openResourceElement(resourceType, id);
    }

    /**
     * Called when a new resource element has just been deleted.
     * @param resourceType Type of resource.
     * @param id Id of the deleted element.
     */
    @Override
    public void resourceElementRemoved(ResourceType resourceType, String id) {

        // Close the editor if any (but it is a bug).
        AbstractEditorPanel editor = tabs.getEditor(id);
        if (editor != null) {
            tabs.removeEditor(editor, false);
            new IllegalStateException(resourceType.getName() + " '" + id
                    + "' was destroyed but its editor was still open"
                    ).printStackTrace();
        }
    }

    /**
     * Called when a resource element has just been renamed.
     * @param resourceType Type of resource.
     * @param id Id of the element.
     * @param name New human-readable name of the element.
     */
    @Override
    public void resourceElementMoved(ResourceType resourceType, String oldId,
            String newId) {

        // Close the old editor if any (but it is a bug).
        AbstractEditorPanel editor = tabs.getEditor(oldId);
        if (editor != null) {
            tabs.removeEditor(editor, false);
            new IllegalStateException(resourceType.getName() + " Id '" + oldId
                    + "' was changed from '" + oldId + "' to '" + newId
                    + "' but its editor was still open").printStackTrace();
        }
        closeResourceElement(resourceType, oldId, false);
    }

    /**
     * Called when a resource element has just been renamed.
     * @param resourceType Type of resource.
     * @param id Id of the element.
     * @param name New human-readable name of the element.
     */
    @Override
    public void resourceElementRenamed(ResourceType resourceType,
            String id, String name) {

        String editorId = null;

        switch (resourceType) {

        case MAP: editorId = MapEditorPanel.getEditorId(id); break;
        case TILESET: editorId = TilesetEditorPanel.getEditorId(id); break;
        case SPRITE: editorId = SpriteEditorPanel.getEditorId(id); break;

        case LANGUAGE:
        case ENEMY:
        case ITEM:
        case ENTITY:
        case SOUND:
        case MUSIC:
        case FONT:
            // No need to refresh title for these kinds of resources.
            break;
        }

        if (editorId != null) {
            tabs.refreshEditorTitle(editorId);
        }
    }

    /**
     * Returns a map currently open in an editor, given its id.
     * @param mapId Id of the map to get.
     * @return The corresponding map, or null if this map is not open.
     */
    public Map getOpenMap(String mapId) {

        MapEditorPanel mapEditor = getOpenMapEditor(mapId);
        if (mapEditor != null) {
            return mapEditor.getMap();
        }
        return null;
    }

    /**
     * Returns the editor of a map currently open, given the map id.
     * @param mapId Id of the map to get.
     * @return The corresponding map editor, or null if this map is not open.
     */
    public MapEditorPanel getOpenMapEditor(String mapId) {

        String editorId = MapEditorPanel.getEditorId(mapId);
        AbstractEditorPanel editor = tabs.getEditor(editorId);
        return (MapEditorPanel) editor;
    }

    /**
     * Returns all currently open editors of the specified resource type.
     * @param resourceType A type of resource.
     * @return The list of editors of this resource type currently open.
     */
    public Collection<AbstractEditorPanel> getOpenEditors(ResourceType resourceType) {
        return tabs.getEditors(resourceType);
    }

   /**
    * Dialog shown when we want to create a new lua script
    */
   private class NewLuaScriptDialog extends OkCancelDialog {
           private static final long serialVersionUID = 1L;

        // Subcomponents
        private final JTextField nameField;

        /**
         * Constructor.
         */
        public NewLuaScriptDialog(String name) {

            super("New lua script", false);

            JPanel mainPanel = new JPanel(new GridBagLayout());
            GridBagConstraints constraints = new GridBagConstraints();
            constraints.insets = new Insets(5, 5, 5, 5); // margins
            constraints.anchor = GridBagConstraints.LINE_START;
            constraints.gridy = 0;
            constraints.gridx = 0;

            mainPanel.add(new JLabel("name:"), constraints);

            constraints.gridx++;
            nameField = new JTextField(15);
            nameField.setText(name);
            mainPanel.add(nameField, constraints);

            setComponent(mainPanel);
        }

        /**
         * Returns the name of lua script file.
         * @return the file name
         */
        public String getFileName() {

            String name = nameField.getText();

            if (!name.endsWith(".lua")) {
                name += ".lua";
            }

            return name;
        }

        @Override
        protected void applyModifications() {
        }
   }

   /**
    * Dialog shown when we want to create a new directory
    */
   private class NewDirectoryDialog extends OkCancelDialog {
           private static final long serialVersionUID = 1L;

        // Subcomponents
        private final JTextField nameField;

        /**
         * Constructor.
         */
        public NewDirectoryDialog(String name) {

            super("New directory", false);

            JPanel mainPanel = new JPanel(new GridBagLayout());
            GridBagConstraints constraints = new GridBagConstraints();
            constraints.insets = new Insets(5, 5, 5, 5); // margins
            constraints.anchor = GridBagConstraints.LINE_START;
            constraints.gridy = 0;
            constraints.gridx = 0;

            mainPanel.add(new JLabel("name:"), constraints);

            constraints.gridx++;
            nameField = new JTextField(15);
            nameField.setText(name);
            mainPanel.add(nameField, constraints);

            setComponent(mainPanel);
        }

        /**
         * Returns the name of lua script file.
         * @return the file name
         */
        public String getDirName() {

            return nameField.getText();
        }

        @Override
        protected void applyModifications() {
        }
   }
}
TOP

Related Classes of org.solarus.editor.gui.EditorWindow

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.