/******************************************************************************* * Copyright 2014 Rafael Garcia Moreno. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.bladecoder.engineeditor.model; import java.beans.PropertyChangeEvent; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.lwjgl.opengl.Display; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.SerializationException; import com.bladecoder.engine.actions.ActionFactory; import com.bladecoder.engine.assets.EngineAssetManager; import com.bladecoder.engine.model.BaseActor; import com.bladecoder.engine.model.Scene; import com.bladecoder.engine.model.World; import com.bladecoder.engine.util.Config; import com.bladecoder.engine.util.EngineLogger; import com.bladecoder.engineeditor.Ctx; import com.bladecoder.engineeditor.common.EditorLogger; import com.bladecoder.engineeditor.common.FolderClassLoader; import com.bladecoder.engineeditor.common.OrderedProperties; import com.bladecoder.engineeditor.common.RunProccess; import com.bladecoder.engineeditor.common.Versions; import com.bladecoder.engineeditor.setup.BladeEngineSetup; import com.bladecoder.engineeditor.setup.Dependency; import com.bladecoder.engineeditor.setup.DependencyBank; import com.bladecoder.engineeditor.setup.DependencyBank.ProjectDependency; import com.bladecoder.engineeditor.setup.DependencyBank.ProjectType; import com.bladecoder.engineeditor.setup.ProjectBuilder; import com.bladecoder.engineeditor.undo.UndoStack; public class Project extends PropertyChange { public static final String PROP_PROJECTFILE = "projectFile"; public static final String NOTIFY_SCENE_SELECTED = "SCENE_SELECTED"; public static final String NOTIFY_ACTOR_SELECTED = "ACTOR_SELECTED"; public static final String NOTIFY_ANIM_SELECTED = "ANIM_SELECTED"; public static final String NOTIFY_VERB_SELECTED = "VERB_SELECTED"; public static final String NOTIFY_PROJECT_LOADED = "PROJECT_LOADED"; public static final String NOTIFY_PROJECT_SAVED = "PROJECT_SAVED"; public static final String NOTIFY_ELEMENT_DELETED = "ELEMENT_DELETED"; public static final String NOTIFY_ELEMENT_CREATED = "ELEMENT_CREATED"; public static final String NOTIFY_MODEL_MODIFIED = "MODEL_MODIFIED"; public static final String POSITION_PROPERTY = "pos"; public static final String WIDTH_PROPERTY = "width"; public static final String HEIGHT_PROPERTY = "height"; public static final String CHAPTER_PROPERTY = "chapter"; public static final String SPINE_RENDERER_STRING = "spine"; public static final String ATLAS_RENDERER_STRING = "atlas"; public static final String IMAGE_RENDERER_STRING = "image"; public static final String S3D_RENDERER_STRING = "3d"; public static final String PARTICLE_RENDERER_STRING = "particle"; public static final String TEXT_RENDERER_STRING = "text"; public static final String ASSETS_PATH = "/android/assets"; public static final String MODEL_PATH = ASSETS_PATH + "/model"; public static final String ATLASES_PATH = ASSETS_PATH + "/atlases"; public static final String FONTS_PATH = ASSETS_PATH + "/fonts"; public static final String MUSIC_PATH = ASSETS_PATH + "/music"; public static final String SOUND_PATH = ASSETS_PATH + "/sounds"; public static final String IMAGE_PATH = ASSETS_PATH + "/images"; public static final String SPRITE3D_PATH = ASSETS_PATH + "/3d"; public static final String SPINE_PATH = ASSETS_PATH + "/spine"; public static final String PARTICLE_PATH = ASSETS_PATH + "/particles"; public static final String VOICE_PATH = ASSETS_PATH + "/voices"; public static final String UI_PATH = ASSETS_PATH + "/ui"; public static final String FONT_PATH = UI_PATH + "/fonts"; public static final int DEFAULT_WIDTH = 1920; public static final int DEFAULT_HEIGHT = 1080; private static final String CONFIG_DIR = System.getProperty("user.home") + "/.AdventureEditor"; private static final String CONFIG_FILENAME = "config.properties"; public static final String LAST_PROJECT_PROP = "last_project"; private final Properties editorConfig = new Properties(); private File projectFile; private final UndoStack undoStack = new UndoStack(); private Properties projectConfig; private I18NHandler i18n; private Chapter chapter; private Scene selectedScene; private BaseActor selectedActor; private String selectedFA; private boolean modified = false; public Project() { loadConfig(); } public UndoStack getUndoStack() { return undoStack; } private void loadConfig() { File dir = new File(CONFIG_DIR); File f = new File(CONFIG_DIR + "/" + CONFIG_FILENAME); if (!dir.exists()) dir.mkdirs(); try { if (!f.exists()) { f.createNewFile(); } else { editorConfig.load(new FileInputStream(f)); } } catch (IOException e) { EditorLogger.error(e.getMessage()); } } public void saveConfig() { File f = new File(CONFIG_DIR + "/" + CONFIG_FILENAME); try { editorConfig.store(new FileOutputStream(f), null); } catch (IOException e) { EditorLogger.error(e.getMessage()); } } public Properties getEditorConfig() { return editorConfig; } public Properties getProjectConfig() { return projectConfig; } public I18NHandler getI18N() { return i18n; } public String translate(String key) { return i18n.getTranslation(key); } public void setModified(Object source, String property, Object oldValue, Object newValue) { modified = true; PropertyChangeEvent evt = new PropertyChangeEvent(source, property, oldValue, newValue); firePropertyChange(evt); } public void notifyPropertyChange(String property) { firePropertyChange(property); } public void setSelectedScene(Scene scn) { selectedScene = scn; selectedActor = null; selectedFA = null; if(scn != null) Ctx.project.getEditorConfig().setProperty("project.selectedScene", scn.getId()); firePropertyChange(NOTIFY_SCENE_SELECTED, null, selectedScene); } public void setSelectedActor(BaseActor a) { BaseActor old = null; old = selectedActor; selectedActor = a; selectedFA = null; firePropertyChange(NOTIFY_ACTOR_SELECTED, old, selectedActor); } public Chapter getChapter() { return chapter; } public Scene getSelectedScene() { return selectedScene; } public BaseActor getSelectedActor() { return selectedActor; } public String getSelectedFA() { return selectedFA; } public void setSelectedFA(String id) { String old = selectedFA; selectedFA = id; firePropertyChange(NOTIFY_ANIM_SELECTED, old, selectedFA); } public String getModelPath() { return projectFile.getAbsolutePath() + MODEL_PATH; } public String getProjectPath() { return projectFile.getAbsolutePath(); } public File getProjectDir() { return projectFile; } public String getTitle() { if (projectConfig == null) return null; return projectConfig.getProperty(Config.TITLE_PROP, getProjectDir().getName()); } public String getPackageTitle() { return getTitle().replace(" ", "").replace("'", ""); } public void createProject(String projectDir, String name, String pkg, String sdkLocation, boolean spinePlugin) throws IOException { closeProject(); createLibGdxProject(projectDir, name, pkg, "BladeEngine", sdkLocation, spinePlugin); projectFile = new File(projectDir + "/" + name); loadProject(projectFile); } private void createLibGdxProject(String projectDir, String name, String pkg, String mainClass, String sdkLocation, boolean spinePlugin) throws IOException { String sdk = null; if (sdkLocation != null && !sdkLocation.isEmpty()) { sdk = sdkLocation; } else if (System.getenv("ANDROID_HOME") != null) { sdk = System.getenv("ANDROID_HOME"); } DependencyBank bank = new DependencyBank(); ProjectBuilder builder = new ProjectBuilder(bank); List<ProjectType> projects = new ArrayList<ProjectType>(); projects.add(ProjectType.CORE); projects.add(ProjectType.DESKTOP); projects.add(ProjectType.ANDROID); projects.add(ProjectType.IOS); // projects.add(ProjectType.HTML); List<Dependency> dependencies = new ArrayList<Dependency>(); dependencies.add(bank.getDependency(ProjectDependency.GDX)); dependencies.add(bank.getDependency(ProjectDependency.FREETYPE)); if (spinePlugin) dependencies.add(bank.getDependency(ProjectDependency.SPINE)); builder.buildProject(projects, dependencies); builder.build(); new BladeEngineSetup().build(builder, projectDir + "/" + name, name, pkg, mainClass, sdk, null); } public void saveProject() throws IOException { if (projectFile != null && chapter.getId() != null && modified) { EngineLogger.setDebug(); // 1.- SAVE world.json World.getInstance().saveWorldDesc( new FileHandle(new File(projectFile.getAbsolutePath() + MODEL_PATH + "/world.json"))); // 2.- SAVE .chapter chapter.save(); // 3.- SAVE BladeEngine.properties projectConfig.store(new FileOutputStream(projectFile.getAbsolutePath() + "/" + ASSETS_PATH + "/" + Config.PROPERTIES_FILENAME), null); // 4.- SAVE I18N i18n.save(); modified = false; firePropertyChange(NOTIFY_PROJECT_SAVED); } } public void closeProject() { setSelectedScene(null); this.projectFile = null; } public void loadProject(File projectToLoad) throws IOException { projectToLoad = checkProjectStructure(projectToLoad); if (projectToLoad != null) { // dispose the current project closeProject(); this.projectFile = projectToLoad; // Use FolderClassLoader for loading CUSTOM actions. // TODO Add 'core/bin' and '/core/out' folders??? FolderClassLoader folderClassLoader = new FolderClassLoader(projectFile.getAbsolutePath() + "/core/build/classes/main"); ActionFactory.setActionClassLoader(folderClassLoader); EngineAssetManager.createEditInstance(Ctx.project.getProjectDir().getAbsolutePath() + Project.ASSETS_PATH); try { World.getInstance().loadWorldDesc(); } catch (SerializationException ex) { // check for not compiled custom actions if (ex.getCause() != null && ex.getCause() instanceof ClassNotFoundException) { EditorLogger.debug("Custom action class not found. Trying to compile..."); if (RunProccess.runGradle(Ctx.project.getProjectDir(), "desktop:compileJava")) { folderClassLoader.reload(); World.getInstance().loadWorldDesc(); } else { this.projectFile = null; throw new IOException("Failed to run Gradle."); } } else { this.projectFile = null; throw ex; } } chapter = new Chapter(Ctx.project.getProjectDir().getAbsolutePath() + Project.MODEL_PATH); i18n = new I18NHandler(Ctx.project.getProjectDir().getAbsolutePath() + Project.MODEL_PATH); // No need to load the chapter. It's loaded by the chapter combo. // loadChapter(World.getInstance().getInitChapter()); editorConfig.setProperty(LAST_PROJECT_PROP, projectFile.getAbsolutePath()); projectConfig = new OrderedProperties(); projectConfig.load(new FileInputStream(projectFile.getAbsolutePath() + ASSETS_PATH + "/" + Config.PROPERTIES_FILENAME)); modified = false; Display.setTitle( "Adventure Editor v" + Versions.getVersion() + " - " + projectFile.getAbsolutePath() ); firePropertyChange(NOTIFY_PROJECT_LOADED); } else { throw new IOException("Project not found."); } } public boolean checkVersion() throws FileNotFoundException, IOException { String editorVersion = getEditorBladeEngineVersion(); String projectVersion = getProjectBladeEngineVersion(); if (editorVersion.equals(projectVersion) || editorVersion.endsWith("SNAPSHOT") || editorVersion.indexOf('.') == -1) return true; if (parseVersion(editorVersion) <= parseVersion(projectVersion)) return true; return false; } private int parseVersion(String v) { int number = 0; String[] split = v.split("\\."); try { for (int i = 0; i < split.length; i++) { number += Math.pow(10, (split.length - i) * 2) * Integer.parseInt(split[i]); } } catch (NumberFormatException e) { } return number; } public String getProjectBladeEngineVersion() throws FileNotFoundException, IOException { Properties properties = getGradleProperties(); return properties.getProperty(Config.BLADE_ENGINE_VERSION_PROP, "default"); } public String getEditorBladeEngineVersion() { return Versions.getVersion(); } public void updateEngineVersion() throws FileNotFoundException, IOException { Properties prop = getGradleProperties(); prop.setProperty(Config.BLADE_ENGINE_VERSION_PROP, Versions.getVersion()); prop.setProperty("gdxVersion", Versions.getLibgdxVersion()); prop.setProperty("roboVMVersion", Versions.getRoboVMVersion()); prop.setProperty("roboVMGradlePluginVersion", Versions.getROBOVMGradlePluginVersion()); prop.setProperty("androidGradlePluginVersion", Versions.getAndroidGradlePluginVersion()); saveGradleProperties(prop); } /** * Checks if the model folder exists in the passed folder or in his parent. * * @return The correct project folder or null if the model folder is not found. */ private File checkProjectStructure(File folder) { File projectFolder = folder; if (!new File(projectFolder.getAbsolutePath() + MODEL_PATH).exists()) { projectFolder = projectFolder.getParentFile(); if (!new File(projectFolder.getAbsolutePath() + MODEL_PATH).exists()) return null; } return projectFolder; } public BaseActor getActor(String id) { return selectedScene.getActor(id, false); } public List<String> getResolutions() { File atlasesPath = new File(projectFile.getAbsolutePath() + ATLASES_PATH); ArrayList<String> l = new ArrayList<String>(); File[] list = atlasesPath.listFiles(); if (list == null) return l; for (int i = 0; i < list.length; i++) { String name = list[i].getName(); if (list[i].isDirectory()) { try { Float.parseFloat(name); l.add(name); } catch (Exception e) { } } } return l; } public String getResDir() { return "1"; } public void loadChapter(String selChapter) throws IOException { undoStack.clear(); setSelectedScene(null); try { chapter.load(selChapter); Ctx.project.getEditorConfig().setProperty("project.selectedChapter", selChapter); } catch (SerializationException ex) { // check for not compiled custom actions if (ex.getCause() != null && ex.getCause() instanceof ClassNotFoundException) { EditorLogger.debug("Custom action class not found. Trying to compile..."); if (RunProccess.runGradle(Ctx.project.getProjectDir(), "desktop:compileJava")) { FolderClassLoader folderClassLoader = new FolderClassLoader(projectFile.getAbsolutePath() + "/core/build/classes/main"); ActionFactory.setActionClassLoader(folderClassLoader); chapter.load(selChapter); } else { throw new IOException("Failed to run Gradle."); } } else { throw ex; } } i18n.load(selChapter); } public boolean isModified() { return modified; } public void setModified() { modified = true; firePropertyChange(NOTIFY_MODEL_MODIFIED); } public Properties getGradleProperties() throws FileNotFoundException, IOException { Properties prop = new Properties(); prop.load(new FileReader(Ctx.project.getProjectDir().getAbsolutePath() + "/gradle.properties")); return prop; } public void saveGradleProperties(Properties prop) throws IOException { FileOutputStream os = new FileOutputStream(Ctx.project.getProjectDir().getAbsolutePath() + "/gradle.properties"); prop.store(os, null); } }