diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/Launcher.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/Launcher.java index 06716e4eb..5a01f708d 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/Launcher.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/Launcher.java @@ -27,7 +27,7 @@ import java.net.URLClassLoader; import java.util.ArrayList; import javax.swing.SwingUtilities; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.StrUtils; import org.jackhuang.hellominecraft.utils.views.LogWindow; import org.jackhuang.hellominecraft.launcher.utils.MinecraftCrashAdvicer; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/Main.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/Main.java index 0ca551d81..301341b96 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/Main.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/Main.java @@ -32,7 +32,7 @@ import javax.net.ssl.X509TrustManager; import javax.swing.ImageIcon; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.api.PluginManager; import org.jackhuang.hellominecraft.launcher.core.launch.GameLauncher; import org.jackhuang.hellominecraft.launcher.utils.CrashReporter; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/api/PluginManager.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/api/PluginManager.java index 127b6769d..1b60ff676 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/api/PluginManager.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/api/PluginManager.java @@ -17,7 +17,7 @@ */ package org.jackhuang.hellominecraft.launcher.api; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.settings.DefaultPlugin; /** diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/ModInfo.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/ModInfo.java index faf3be1c0..b74bed943 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/ModInfo.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/ModInfo.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.StrUtils; import org.jackhuang.hellominecraft.utils.system.FileUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/assets/AssetsMojangLoader.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/assets/AssetsMojangLoader.java index 8c34e8976..c7aa92f3b 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/assets/AssetsMojangLoader.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/assets/AssetsMojangLoader.java @@ -22,7 +22,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Map; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftAssetService; import org.jackhuang.hellominecraft.utils.tasks.Task; import org.jackhuang.hellominecraft.utils.system.FileUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/assets/IAssetsHandler.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/assets/IAssetsHandler.java index d53db6f1c..94be9621e 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/assets/IAssetsHandler.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/assets/IAssetsHandler.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftAssetService; import org.jackhuang.hellominecraft.launcher.core.download.IDownloadProvider; import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/auth/yggdrasil/PropertyMap.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/auth/yggdrasil/PropertyMap.java index fedfd3b5b..f02b041ef 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/auth/yggdrasil/PropertyMap.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/auth/yggdrasil/PropertyMap.java @@ -31,7 +31,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; public class PropertyMap extends HashMap { diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/auth/yggdrasil/YggdrasilAuthentication.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/auth/yggdrasil/YggdrasilAuthentication.java index 1c66c41d6..65bfe184a 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/auth/yggdrasil/YggdrasilAuthentication.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/auth/yggdrasil/YggdrasilAuthentication.java @@ -29,7 +29,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.NetUtils; import org.jackhuang.hellominecraft.utils.StrUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/download/MinecraftDownloadService.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/download/MinecraftDownloadService.java index b5854c994..b64b64cee 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/download/MinecraftDownloadService.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/download/MinecraftDownloadService.java @@ -23,9 +23,8 @@ import java.io.File; import java.util.ArrayList; import java.util.List; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.GameException; -import org.jackhuang.hellominecraft.launcher.core.launch.GameLauncher; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService; import org.jackhuang.hellominecraft.launcher.core.version.IMinecraftLibrary; import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion; @@ -119,6 +118,19 @@ public class MinecraftDownloadService extends IMinecraftDownloadService { } } + @Override + public boolean downloadMinecraftJarTo(String id, File mvt) { + String vurl = service.getDownloadType().getProvider().getVersionsDownloadURL() + id + "/"; + if (TaskWindow.getInstance() + .addTask(new FileDownloadTask(vurl + id + ".jar", IOUtils.tryGetCanonicalFile(mvt)).setTag(id + ".jar")) + .start()) + return true; + else { + mvt.delete(); + return false; + } + } + @Override public boolean downloadMinecraftVersionJson(String id) { String vurl = service.getDownloadType().getProvider().getVersionsDownloadURL() + id + "/"; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/download/MojangDownloadProvider.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/download/MojangDownloadProvider.java index 7df070d31..9aebf0fc6 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/download/MojangDownloadProvider.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/download/MojangDownloadProvider.java @@ -17,7 +17,6 @@ */ package org.jackhuang.hellominecraft.launcher.core.download; -import org.jackhuang.hellominecraft.launcher.core.installers.InstallerType; import org.jackhuang.hellominecraft.launcher.core.installers.InstallerVersionList; /** diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/PackMinecraftInstaller.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/PackMinecraftInstaller.java index a79ba735c..0dc608e55 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/PackMinecraftInstaller.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/PackMinecraftInstaller.java @@ -43,7 +43,7 @@ public class PackMinecraftInstaller { file.mkdirs(); for (String src1 : src) Compressor.unzip(new File(src1), file); - Compressor.zip(file, dest); + Compressor.zip(file.getAbsolutePath(), dest.getAbsolutePath()); FileUtils.deleteDirectory(file); } } diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/forge/ForgeInstaller.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/forge/ForgeInstaller.java index 1842f9c6e..e19e85473 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/forge/ForgeInstaller.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/forge/ForgeInstaller.java @@ -25,7 +25,7 @@ import java.io.InputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService; import org.jackhuang.hellominecraft.launcher.core.installers.InstallerVersionList.InstallerVersion; import org.jackhuang.hellominecraft.utils.tasks.Task; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/liteloader/LiteLoaderInstaller.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/liteloader/LiteLoaderInstaller.java index d374961d4..6e315c140 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/liteloader/LiteLoaderInstaller.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/installers/liteloader/LiteLoaderInstaller.java @@ -21,7 +21,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService; import org.jackhuang.hellominecraft.utils.tasks.Task; import org.jackhuang.hellominecraft.utils.tasks.communication.PreviousResult; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/AbstractMinecraftLoader.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/AbstractMinecraftLoader.java index c3bd0574a..e0cf1163b 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/AbstractMinecraftLoader.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/AbstractMinecraftLoader.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.Launcher; import org.jackhuang.hellominecraft.launcher.core.GameException; import org.jackhuang.hellominecraft.launcher.core.auth.UserProfileProvider; @@ -132,7 +132,7 @@ public abstract class AbstractMinecraftLoader implements IMinecraftLoader { } @Override - public List makeLaunchingCommand() { + public List makeLaunchingCommand() throws GameException { HMCLog.log("*** Make shell command ***"); ArrayList res = new ArrayList<>(); @@ -191,7 +191,7 @@ public abstract class AbstractMinecraftLoader implements IMinecraftLoader { * * @param list the command list you shoud edit. */ - protected abstract void makeSelf(List list); + protected abstract void makeSelf(List list) throws GameException; protected void appendJVMArgs(List list) { } diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/DefaultGameLauncher.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/DefaultGameLauncher.java index cf5d16410..8ce6ee03f 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/DefaultGameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/DefaultGameLauncher.java @@ -19,7 +19,7 @@ package org.jackhuang.hellominecraft.launcher.core.launch; import java.io.IOException; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.auth.IAuthenticator; import org.jackhuang.hellominecraft.launcher.core.auth.LoginInfo; import org.jackhuang.hellominecraft.launcher.core.download.DownloadLibraryJob; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/GameLauncher.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/GameLauncher.java index 7fda23424..835146c7f 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/GameLauncher.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/GameLauncher.java @@ -27,7 +27,7 @@ import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.util.List; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.api.PluginManager; import org.jackhuang.hellominecraft.launcher.core.GameException; import org.jackhuang.hellominecraft.launcher.core.auth.IAuthenticator; @@ -174,13 +174,10 @@ public class GameLauncher { } writer.write(StrUtils.makeCommand(str)); writer.close(); - if (!isWin) - try { - Runtime.getRuntime().exec("chmod +x " + IOUtils.tryGetCanonicalFilePath(f)); - } catch (IOException e) { - HMCLog.warn("Failed to give sh file permission.", e); - MessageBox.Show(C.i18n("launch.failed_sh_permission")); - } + if (!f.setExecutable(true)) { + HMCLog.warn("Failed to give launcher permission."); + MessageBox.Show(C.i18n("launch.failed_sh_permission")); + } HMCLog.log("Command: " + StrUtils.parseParams("", str, " ")); return f; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/LibraryDownloadTask.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/LibraryDownloadTask.java index 59b87ed4c..521da3592 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/LibraryDownloadTask.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/LibraryDownloadTask.java @@ -28,7 +28,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.jar.Pack200; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.download.DownloadLibraryJob; import org.jackhuang.hellominecraft.utils.tasks.download.FileDownloadTask; import org.jackhuang.hellominecraft.utils.system.IOUtils; @@ -50,23 +50,25 @@ public class LibraryDownloadTask extends FileDownloadTask { @Override public void executeTask() throws Throwable { File packFile = new File(job.path.getParentFile(), job.path.getName() + ".pack.xz"); - if (job.name.contains("typesafe")) { - download(new URL(job.url + ".pack.xz"), packFile); - unpackLibrary(job.path, packFile); - packFile.delete(); - } else { - if (job.name.startsWith("net.minecraftforge:forge:")) { - String[] s = job.name.split(":"); - if (s.length == 3) - job.url = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" + s[2] + "/forge-" + s[2] + "-universal.jar"; - } - if (job.name.startsWith("com.mumfrey:liteloader:")) { - String[] s = job.name.split(":"); - if (s.length == 3 && s[2].length() > 3) - job.url = "http://dl.liteloader.com/versions/com/mumfrey/liteloader/" + s[2].substring(0, s[2].length() - 3) + "/liteloader-" + s[2] + ".jar"; - } - download(new URL(job.url), job.path); + /* + * if (job.name.contains("typesafe")) { + * download(new URL(job.url + ".pack.xz"), packFile); + * unpackLibrary(job.path, packFile); + * packFile.delete(); + * } else { + */ + if (job.name.startsWith("net.minecraftforge:forge:")) { + String[] s = job.name.split(":"); + if (s.length == 3) + job.url = "http://files.minecraftforge.net/maven/net/minecraftforge/forge/" + s[2] + "/forge-" + s[2] + "-universal.jar"; } + if (job.name.startsWith("com.mumfrey:liteloader:")) { + String[] s = job.name.split(":"); + if (s.length == 3 && s[2].length() > 3) + job.url = "http://dl.liteloader.com/versions/com/mumfrey/liteloader/" + s[2].substring(0, s[2].length() - 3) + "/liteloader-" + s[2] + ".jar"; + } + download(new URL(job.url), job.path); + //} } void download(URL url, File filePath) throws Throwable { diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/MinecraftLoader.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/MinecraftLoader.java index de44f4930..abfae8b3c 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/MinecraftLoader.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/launch/MinecraftLoader.java @@ -24,7 +24,7 @@ import java.io.IOException; import java.util.List; import java.util.Map; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.Main; import org.jackhuang.hellominecraft.launcher.core.GameException; import org.jackhuang.hellominecraft.launcher.core.auth.UserProfileProvider; @@ -54,14 +54,17 @@ public class MinecraftLoader extends AbstractMinecraftLoader { } @Override - protected void makeSelf(List res) { + protected void makeSelf(List res) throws GameException { String library = options.isCanceledWrapper() ? "" : "-cp="; for (MinecraftLibrary l : version.libraries) { l.init(); if (l.allow() && !l.isRequiredToUnzip()) library += l.getFilePath(gameDir).getAbsolutePath() + File.pathSeparator; } - library += IOUtils.tryGetCanonicalFilePath(version.getJar(service.baseDirectory())) + File.pathSeparator; + File f = version.getJar(service.baseDirectory()); + if (!f.exists()) + throw new GameException("Minecraft jar does not exists"); + library += IOUtils.tryGetCanonicalFilePath(f) + File.pathSeparator; library = library.substring(0, library.length() - File.pathSeparator.length()); if (options.isCanceledWrapper()) res.add("-cp"); diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/mod/MinecraftModService.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/mod/MinecraftModService.java index 025778af1..d52d13d0c 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/mod/MinecraftModService.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/mod/MinecraftModService.java @@ -25,7 +25,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftModService; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService; import org.jackhuang.hellominecraft.launcher.core.ModInfo; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/mod/ModpackManager.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/mod/ModpackManager.java index 650f11d7d..9cd20a206 100644 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/mod/ModpackManager.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/mod/ModpackManager.java @@ -22,14 +22,20 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileSystemException; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.GameException; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftProvider; +import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService; import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion; import org.jackhuang.hellominecraft.utils.system.Compressor; import org.jackhuang.hellominecraft.utils.system.FileUtils; +import org.jackhuang.hellominecraft.utils.system.ZipEngine; +import org.jackhuang.hellominecraft.utils.version.MinecraftVersionRequest; /** * A mod pack(*.zip) includes these things: @@ -51,8 +57,8 @@ import org.jackhuang.hellominecraft.utils.system.FileUtils; */ public final class ModpackManager { - public static void install(File input, File installFolder, String id) throws IOException, FileAlreadyExistsException { - File versions = new File(installFolder, "versions"); + public static void install(File input, IMinecraftService service, String id) throws IOException, FileAlreadyExistsException { + File versions = new File(service.baseDirectory(), "versions"); File oldFile = new File(versions, "minecraft"), newFile = null; if (oldFile.exists()) { newFile = new File(versions, "minecraft-" + System.currentTimeMillis()); @@ -64,19 +70,29 @@ public final class ModpackManager { } try { - AtomicBoolean b = new AtomicBoolean(false); + AtomicInteger b = new AtomicInteger(0); HMCLog.log("Decompressing modpack"); Compressor.unzip(input, versions, t -> { if (t.equals("minecraft/pack.json")) - b.set(true); + b.incrementAndGet(); + if (t.equals("minecraft/pack.jar")) + b.incrementAndGet(); return true; }); - if (!b.get()) - throw new FileNotFoundException("the mod pack is not in a correct format."); + if (b.get() < 2) + throw new FileNotFoundException("the mod pack is in an incorrect format."); File nowFile = new File(versions, id); oldFile.renameTo(nowFile); - new File(nowFile, "pack.json").renameTo(new File(nowFile, id + ".json")); + File json = new File(nowFile, "pack.json"); + MinecraftVersion mv = C.gson.fromJson(FileUtils.readFileToString(json), MinecraftVersion.class); + if (mv.jar == null) + throw new FileSystemException("the mod pack is in an incorrect format, pack.json does not have attribute 'jar'."); + service.download().downloadMinecraftJarTo(mv.jar, new File(nowFile, id + ".jar")); + mv.jar = null; + FileUtils.writeStringToFile(json, C.gsonPrettyPrinting.toJson(mv)); + json.renameTo(new File(nowFile, id + ".json")); + } finally { FileUtils.deleteDirectoryQuietly(oldFile); if (newFile != null) @@ -94,31 +110,34 @@ public final class ModpackManager { * * @throws IOException if create tmp directory failed */ - public static void export(File output, IMinecraftProvider provider, String version) throws IOException, GameException { - File tmp = new File(System.getProperty("java.io.tmpdir"), "hmcl-modpack"); - tmp.mkdirs(); - - File root = new File(tmp, "minecraft"); - - HMCLog.log("Copying files from game directory."); - FileUtils.copyDirectory(provider.getRunDirectory(version), root); - File pack = new File(root, "pack.json"); - MinecraftVersion mv = provider.getVersionById(version).resolve(provider); + public static void export(File output, IMinecraftProvider provider, String version, List blacklist) throws IOException, GameException { + ArrayList b = new ArrayList<>(Arrays.asList(new String[] { "usernamecache.json", "asm", "logs", "backups", "versions", "assets", "usercache.json", "libraries", "crash-reports", "launcher_profiles.json", "NVIDIA", "TCNodeTracker" })); + if (blacklist != null) + b.addAll(blacklist); + HMCLog.log("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); + ZipEngine zip = null; try { - FileUtils.writeStringToFile(pack, C.gsonPrettyPrinting.toJson(mv)); - String[] blacklist = { "usernamecache.json", "asm", "logs", "backups", "versions", "assets", "usercache.json", "libraries", "crash-reports", "launcher_profiles.json", "NVIDIA", "TCNodeTracker" }; - HMCLog.log("Removing files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker"); - for (String s : blacklist) { - File f = new File(root, s); - if (f.isFile()) - f.delete(); - else if (f.isDirectory()) - FileUtils.deleteDirectory(f); - } - HMCLog.log("Compressing game files"); - Compressor.zip(tmp, output); + zip = new ZipEngine(output); + zip.putDirectory(provider.getRunDirectory(version), (String x, Boolean y) -> { + for (String s : b) + if (y) { + if (x.startsWith(s + "/")) + return null; + } else if (x.equals(s)) + return null; + return "minecraft/" + x; + }); + + MinecraftVersion mv = provider.getVersionById(version).resolve(provider); + mv.runDir = "version"; + MinecraftVersionRequest r = MinecraftVersionRequest.minecraftVersion(provider.getMinecraftJar(version)); + if (r.type != MinecraftVersionRequest.OK) + throw new FileSystemException("Cannot read vanilla version, " + MinecraftVersionRequest.getResponse(r)); + mv.jar = r.version; + zip.putTextFile(C.gsonPrettyPrinting.toJson(mv), "minecraft/pack.json"); } finally { - FileUtils.deleteDirectory(tmp); + if (zip != null) + zip.closeFile(); } } diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/service/IMinecraftDownloadService.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/service/IMinecraftDownloadService.java index f81a76dff..e3e8e51ff 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/service/IMinecraftDownloadService.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/service/IMinecraftDownloadService.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hellominecraft.launcher.core.service; +import java.io.File; import java.util.List; import org.jackhuang.hellominecraft.launcher.core.GameException; import org.jackhuang.hellominecraft.launcher.core.download.DownloadLibraryJob; @@ -38,6 +39,8 @@ public abstract class IMinecraftDownloadService extends IMinecraftBasicService { public abstract boolean downloadMinecraftJar(String id); + public abstract boolean downloadMinecraftJarTo(String id, File f); + public abstract boolean downloadMinecraftVersionJson(String id); /** diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/version/MinecraftVersionManager.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/version/MinecraftVersionManager.java index a7ff4fd57..e82ba3b65 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/version/MinecraftVersionManager.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/core/version/MinecraftVersionManager.java @@ -25,7 +25,7 @@ import java.util.Collection; import java.util.Map; import java.util.TreeMap; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.GameException; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftProvider; import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftService; @@ -36,6 +36,7 @@ import org.jackhuang.hellominecraft.utils.tasks.TaskWindow; import org.jackhuang.hellominecraft.utils.tasks.download.FileDownloadTask; import org.jackhuang.hellominecraft.utils.system.IOUtils; import org.jackhuang.hellominecraft.utils.MessageBox; +import org.jackhuang.hellominecraft.utils.StrUtils; import org.jackhuang.hellominecraft.utils.functions.Consumer; import org.jackhuang.hellominecraft.utils.views.SwingUtils; @@ -232,7 +233,10 @@ public class MinecraftVersionManager extends IMinecraftProvider { @Override public File getMinecraftJar(String id) { - return versions.get(id).getJar(service.baseDirectory()); + if (versions.containsKey(id)) + return versions.get(id).getJar(service.baseDirectory()); + else + return null; } @Override @@ -242,7 +246,7 @@ public class MinecraftVersionManager extends IMinecraftProvider { @Override public MinecraftVersion getVersionById(String id) { - return id == null ? null : versions.get(id); + return StrUtils.isBlank(id) ? null : versions.get(id); } @Override diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/settings/Profile.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/settings/Profile.java index 22bc2fd72..6d2b7c0f2 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/settings/Profile.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/settings/Profile.java @@ -20,7 +20,7 @@ package org.jackhuang.hellominecraft.launcher.settings; import java.io.File; import java.io.IOException; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.Main; import org.jackhuang.hellominecraft.launcher.api.PluginManager; import org.jackhuang.hellominecraft.launcher.core.LauncherVisibility; @@ -115,7 +115,7 @@ public final class Profile { public String getSelectedVersion() { String v = selectedMinecraftVersion; - if (v == null) { + if (StrUtils.isBlank(v) || service.version().getVersionById(v) == null) { v = service.version().getOneVersion().id; if (v != null) setSelectedMinecraftVersion(v); diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/settings/Settings.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/settings/Settings.java index 90a594a70..ccd35fe8f 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/settings/Settings.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/settings/Settings.java @@ -26,7 +26,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.Main; import org.jackhuang.hellominecraft.launcher.core.download.DownloadType; import org.jackhuang.hellominecraft.utils.CollectionUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/CrashReporter.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/CrashReporter.java index 221ce437d..151338dfa 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/CrashReporter.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/CrashReporter.java @@ -24,7 +24,7 @@ import java.util.HashMap; import java.util.HashSet; import javax.swing.SwingUtilities; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.Main; import org.jackhuang.hellominecraft.launcher.settings.Settings; import org.jackhuang.hellominecraft.utils.NetUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/upgrade/AppDataUpgrader.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/upgrade/AppDataUpgrader.java index eade88faa..6ebf647cf 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/upgrade/AppDataUpgrader.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/upgrade/AppDataUpgrader.java @@ -35,7 +35,7 @@ import java.util.jar.JarOutputStream; import java.util.jar.Pack200; import java.util.zip.GZIPInputStream; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.MCUtils; import org.jackhuang.hellominecraft.utils.tasks.Task; import org.jackhuang.hellominecraft.utils.tasks.TaskWindow; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/upgrade/NewFileUpgrader.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/upgrade/NewFileUpgrader.java index 0e00b9d3a..63b8b84ff 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/upgrade/NewFileUpgrader.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/utils/upgrade/NewFileUpgrader.java @@ -21,7 +21,7 @@ import java.io.File; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.tasks.TaskWindow; import org.jackhuang.hellominecraft.utils.tasks.download.FileDownloadTask; import org.jackhuang.hellominecraft.utils.ArrayUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameDownloadPanel.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameDownloadPanel.java index 53cf925b0..fafa1808c 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameDownloadPanel.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameDownloadPanel.java @@ -19,7 +19,7 @@ package org.jackhuang.hellominecraft.launcher.views; import javax.swing.table.DefaultTableModel; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.MessageBox; import org.jackhuang.hellominecraft.utils.StrUtils; import org.jackhuang.hellominecraft.utils.views.SwingUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameSettingsPanel.form b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameSettingsPanel.form index 5a341e708..56320d25d 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameSettingsPanel.form +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameSettingsPanel.form @@ -346,7 +346,9 @@ - + + + @@ -354,7 +356,9 @@ - + + + diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameSettingsPanel.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameSettingsPanel.java index 165bcb556..319dc1700 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameSettingsPanel.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/GameSettingsPanel.java @@ -32,7 +32,9 @@ import java.awt.event.ItemEvent; import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; +import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JFileChooser; @@ -45,7 +47,7 @@ import javax.swing.event.TableModelEvent; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.DefaultTableModel; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.GameException; import org.jackhuang.hellominecraft.launcher.core.LauncherVisibility; import org.jackhuang.hellominecraft.launcher.settings.Profile; @@ -56,6 +58,8 @@ import org.jackhuang.hellominecraft.launcher.core.installers.InstallerType; import org.jackhuang.hellominecraft.launcher.core.mod.ModpackManager; import org.jackhuang.hellominecraft.launcher.core.version.GameDirType; import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion; +import org.jackhuang.hellominecraft.launcher.views.modpack.ModpackInitializationPanel; +import org.jackhuang.hellominecraft.launcher.views.modpack.ModpackWizard; import org.jackhuang.hellominecraft.utils.tasks.TaskRunnable; import org.jackhuang.hellominecraft.utils.tasks.TaskWindow; import org.jackhuang.hellominecraft.utils.Event; @@ -64,8 +68,10 @@ import org.jackhuang.hellominecraft.utils.MessageBox; import org.jackhuang.hellominecraft.utils.version.MinecraftVersionRequest; import org.jackhuang.hellominecraft.utils.system.OS; import org.jackhuang.hellominecraft.utils.StrUtils; +import org.jackhuang.hellominecraft.utils.system.FileUtils; import org.jackhuang.hellominecraft.utils.views.SwingUtils; import org.jackhuang.hellominecraft.utils.system.Java; +import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer; import rx.Observable; import rx.concurrency.Schedulers; @@ -403,14 +409,14 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget } }); - btnExportModpack.setText("导出整合包"); + btnExportModpack.setText(C.i18n("settings.modpack.save.task")); // NOI18N btnExportModpack.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnExportModpackActionPerformed(evt); } }); - btnImportModpack.setText("导入整合包"); + btnImportModpack.setText(C.i18n("settings.modpack.install.task")); // NOI18N btnImportModpack.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnImportModpackActionPerformed(evt); @@ -914,6 +920,7 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget // private void cboProfilesItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboProfilesItemStateChanged if (!isLoading) { + Settings.getInstance().setLast((String) cboProfiles.getSelectedItem()); if (getProfile().service().version().getVersionCount() <= 0) versionChanged(null); prepare(getProfile()); @@ -1139,13 +1146,14 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget fc.setFileSelectionMode(JFileChooser.FILES_ONLY); fc.setDialogTitle(C.i18n("settings.modpack.choose")); fc.setMultiSelectionEnabled(false); - fc.setFileFilter(new FileNameExtensionFilter(C.i18n("settings.modpack"), ".zip")); + fc.setFileFilter(new FileNameExtensionFilter(C.i18n("settings.modpack"), "zip")); fc.showOpenDialog(this); if (fc.getSelectedFile() == null) return; - TaskWindow.getInstance().addTask(new TaskRunnable(C.i18n("settings.modpack"), () -> { + String suggestedModpackId = JOptionPane.showInputDialog("Please enter your favourite game name", FileUtils.getBaseName(fc.getSelectedFile().getName())); + TaskWindow.getInstance().addTask(new TaskRunnable(C.i18n("settings.modpack.install.task"), () -> { try { - ModpackManager.install(fc.getSelectedFile(), getProfile().getCanonicalGameDirFile(), fc.getSelectedFile().getName()); + ModpackManager.install(fc.getSelectedFile(), getProfile().service(), suggestedModpackId); } catch (IOException ex) { MessageBox.Show(C.i18n("settings.modpack.install_error")); HMCLog.err("Failed to install modpack", ex); @@ -1154,22 +1162,20 @@ public final class GameSettingsPanel extends AnimatedPanel implements DropTarget }//GEN-LAST:event_btnImportModpackActionPerformed private void btnExportModpackActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExportModpackActionPerformed - JFileChooser fc = new JFileChooser(); - fc.setFileSelectionMode(JFileChooser.FILES_ONLY); - fc.setDialogTitle(C.i18n("settings.modpack.save")); - fc.setMultiSelectionEnabled(false); - fc.setFileFilter(new FileNameExtensionFilter(C.i18n("settings.modpack"), ".zip")); - fc.showSaveDialog(this); - if (fc.getSelectedFile() == null) - return; - TaskWindow.getInstance().addTask(new TaskRunnable(C.i18n("settings.modpack"), () -> { - try { - ModpackManager.export(fc.getSelectedFile(), getProfile().service().version(), getProfile().getSelectedVersion()); - } catch (IOException | GameException ex) { - MessageBox.Show(C.i18n("settings.modpack.export_error")); - HMCLog.err("Failed to export modpack", ex); - } - })).start(); + Map settings = (Map) WizardDisplayer.showWizard(new ModpackWizard(getProfile().service().version()).createWizard()); + if (settings != null) + TaskWindow.getInstance().addTask(new TaskRunnable(C.i18n("settings.modpack.save.task"), + () -> { + try { + ModpackManager.export(new File((String) settings.get(ModpackInitializationPanel.KEY_MODPACK_LOCATION)), + getProfile().service().version(), + (String) settings.get(ModpackInitializationPanel.KEY_GAME_VERSION), + ((Boolean) settings.get(ModpackInitializationPanel.KEY_SAVE) == false) ? Arrays.asList("saves") : null); + } catch (IOException | GameException ex) { + MessageBox.Show(C.i18n("settings.modpack.export_error")); + HMCLog.err("Failed to export modpack", ex); + } + })).start(); }//GEN-LAST:event_btnExportModpackActionPerformed // diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/LauncherSettingsPanel.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/LauncherSettingsPanel.java index 6a6bf1188..7b8ee68d6 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/LauncherSettingsPanel.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/LauncherSettingsPanel.java @@ -23,7 +23,7 @@ import javax.swing.DefaultComboBoxModel; import javax.swing.JFileChooser; import javax.swing.filechooser.FileNameExtensionFilter; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.settings.Settings; import org.jackhuang.hellominecraft.launcher.core.download.DownloadType; import org.jackhuang.hellominecraft.utils.system.IOUtils; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/MainFrame.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/MainFrame.java index 8648cb724..da4727cc3 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/MainFrame.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/MainFrame.java @@ -41,7 +41,7 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.Main; import org.jackhuang.hellominecraft.launcher.settings.Settings; import org.jackhuang.hellominecraft.launcher.core.auth.IAuthenticator; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/MainPagePanel.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/MainPagePanel.java index 5b3eb5850..721341840 100755 --- a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/MainPagePanel.java +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/MainPagePanel.java @@ -28,7 +28,7 @@ import java.util.List; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.launcher.core.launch.DefaultGameLauncher; import org.jackhuang.hellominecraft.launcher.core.GameException; import org.jackhuang.hellominecraft.launcher.core.auth.IAuthenticator; diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackInitializationPanel.form b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackInitializationPanel.form new file mode 100644 index 000000000..7b0fef676 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackInitializationPanel.form @@ -0,0 +1,110 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackInitializationPanel.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackInitializationPanel.java new file mode 100644 index 000000000..4f26e0f40 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackInitializationPanel.java @@ -0,0 +1,187 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2013 huangyuhui + * + * 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 org.jackhuang.hellominecraft.launcher.views.modpack; + +import java.io.File; +import java.util.Map; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; +import org.jackhuang.hellominecraft.utils.C; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardController; + +/** + * + * @author huangyuhui + */ +public class ModpackInitializationPanel extends javax.swing.JPanel { + + public static final String KEY_GAME_VERSION = "gameVersion"; + public static final String KEY_MODPACK_LOCATION = "modpackLocation"; + public static final String KEY_SAVE = "save"; + + private final WizardController controller; + private final Map wizardData; + + /** + * Creates new form ModpackInitializationPanel + */ + public ModpackInitializationPanel(WizardController controller, Map wizardData, String[] versions) { + initComponents(); + + this.controller = controller; + this.wizardData = wizardData; + wizardData.put(KEY_GAME_VERSION, versions); + + wizardData.put(KEY_SAVE, false); + + configureComboContents(); + controller.setProblem("Not a valid file location"); + + controller.setForwardNavigationMode(WizardController.MODE_CAN_FINISH); + } + + private void configureComboContents() { + String[] versions = (String[]) wizardData.get(KEY_GAME_VERSION); + cboGameVersion.setModel(new DefaultComboBoxModel<>(versions)); + + } + + /** + * This method is called from within the constructor to + * initialize the form. + * WARNING: Do NOT modify this code. The content of this method is + * always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + lblModpackLocation = new javax.swing.JLabel(); + txtModpackLocation = new javax.swing.JTextField(); + cboModpackLocation = new javax.swing.JButton(); + lblGameVersion = new javax.swing.JLabel(); + cboGameVersion = new javax.swing.JComboBox<>(); + chkSave = new javax.swing.JCheckBox(); + + lblModpackLocation.setText(C.i18n("settings.modpack.save")); // NOI18N + + txtModpackLocation.addCaretListener(new javax.swing.event.CaretListener() { + public void caretUpdate(javax.swing.event.CaretEvent evt) { + txtModpackLocationCaretUpdate(evt); + } + }); + + cboModpackLocation.setText("..."); + cboModpackLocation.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + cboModpackLocationActionPerformed(evt); + } + }); + + lblGameVersion.setText("要导出的游戏版本"); + + cboGameVersion.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + cboGameVersionItemStateChanged(evt); + } + }); + + chkSave.setText("允许导出存档"); + chkSave.addItemListener(new java.awt.event.ItemListener() { + public void itemStateChanged(java.awt.event.ItemEvent evt) { + chkSaveItemStateChanged(evt); + } + }); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addComponent(lblModpackLocation) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 175, Short.MAX_VALUE) + .addComponent(cboModpackLocation)) + .addComponent(txtModpackLocation) + .addComponent(cboGameVersion, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(lblGameVersion) + .addComponent(chkSave)) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(lblModpackLocation) + .addComponent(cboModpackLocation)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(txtModpackLocation, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(29, 29, 29) + .addComponent(lblGameVersion) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(cboGameVersion, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(chkSave) + .addContainerGap(133, Short.MAX_VALUE)) + ); + }// //GEN-END:initComponents + + private void cboModpackLocationActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cboModpackLocationActionPerformed + JFileChooser fc = new JFileChooser(); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setDialogTitle(C.i18n("settings.modpack.save")); + fc.setMultiSelectionEnabled(false); + fc.setFileFilter(new FileNameExtensionFilter(C.i18n("settings.modpack") + "(*.zip)", "zip")); + fc.showSaveDialog(this); + if (fc.getSelectedFile() != null) + txtModpackLocation.setText(fc.getSelectedFile().getAbsolutePath()); + }//GEN-LAST:event_cboModpackLocationActionPerformed + + private void txtModpackLocationCaretUpdate(javax.swing.event.CaretEvent evt) {//GEN-FIRST:event_txtModpackLocationCaretUpdate + wizardData.put(KEY_MODPACK_LOCATION, txtModpackLocation.getText()); + + if (txtModpackLocation.getText().trim().isEmpty()) + controller.setProblem("Please choose a location!!!"); + else + controller.setProblem(null); + }//GEN-LAST:event_txtModpackLocationCaretUpdate + + private void chkSaveItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkSaveItemStateChanged + wizardData.put(KEY_SAVE, chkSave.isSelected()); + }//GEN-LAST:event_chkSaveItemStateChanged + + private void cboGameVersionItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboGameVersionItemStateChanged + wizardData.put(KEY_GAME_VERSION, cboGameVersion.getSelectedItem()); + }//GEN-LAST:event_cboGameVersionItemStateChanged + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JComboBox cboGameVersion; + private javax.swing.JButton cboModpackLocation; + private javax.swing.JCheckBox chkSave; + private javax.swing.JLabel lblGameVersion; + private javax.swing.JLabel lblModpackLocation; + private javax.swing.JTextField txtModpackLocation; + // End of variables declaration//GEN-END:variables +} diff --git a/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackWizard.java b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackWizard.java new file mode 100644 index 000000000..73790c1d2 --- /dev/null +++ b/HMCL/src/main/java/org/jackhuang/hellominecraft/launcher/views/modpack/ModpackWizard.java @@ -0,0 +1,59 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2013 huangyuhui + * + * 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 org.jackhuang.hellominecraft.launcher.views.modpack; + +import java.util.Iterator; +import java.util.Map; +import javax.swing.JComponent; +import org.jackhuang.hellominecraft.launcher.core.service.IMinecraftProvider; +import org.jackhuang.hellominecraft.launcher.core.version.MinecraftVersion; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardBranchController; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardController; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardPanelProvider; + +/** + * + * @author huangyuhui + */ +public class ModpackWizard extends WizardBranchController { + + public ModpackWizard(IMinecraftProvider provider) { + super(new WizardPanelProvider("Modpack Wizard", new String[] { "Settings" }, new String[] { "Select location, version and allow version" }) { + + @Override + protected JComponent createPanel(WizardController controller, String id, Map settings) { + switch (indexOfStep(id)) { + case 0: + String[] s = new String[provider.getVersionCount()]; + Iterator it = provider.getVersions().iterator(); + for (int i = 0; i < s.length; i++) + s[i] = it.next().id; + return new ModpackInitializationPanel(controller, settings, s); + default: + throw new IllegalArgumentException(id); + } + } + }); + } + + @Override + protected WizardPanelProvider getPanelProviderForStep(String step, Map settings) { + return null; + } + +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/C.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/C.java index fa2aed1f2..2b6eace29 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/C.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/C.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hellominecraft.utils; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import java.util.ResourceBundle; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/NetUtils.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/NetUtils.java index a0889ba7e..ce800bf0c 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/NetUtils.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/NetUtils.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hellominecraft.utils; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/UpdateChecker.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/UpdateChecker.java index 61802f023..b4db4315e 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/UpdateChecker.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/UpdateChecker.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hellominecraft.utils; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import java.util.Map; import rx.Observable; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/Utils.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/Utils.java index c41e3c1b7..aae27b9b4 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/Utils.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/Utils.java @@ -17,6 +17,7 @@ */ package org.jackhuang.hellominecraft.utils; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import com.sun.management.OperatingSystemMXBean; import java.awt.Image; import java.awt.Toolkit; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/VersionNumber.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/VersionNumber.java index d37913719..0773299aa 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/VersionNumber.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/VersionNumber.java @@ -17,6 +17,8 @@ */ package org.jackhuang.hellominecraft.utils; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; + /** * * @author huangyuhui diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/HMCLog.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/logging/HMCLog.java similarity index 96% rename from HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/HMCLog.java rename to HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/logging/HMCLog.java index 4c23b80f7..4bbcfe387 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/HMCLog.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/logging/HMCLog.java @@ -15,7 +15,7 @@ * 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.jackhuang.hellominecraft.utils; +package org.jackhuang.hellominecraft.utils.logging; import org.jackhuang.hellominecraft.utils.logging.logger.Logger; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/Compressor.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/Compressor.java index e89ec7355..ed8e4741d 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/Compressor.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/Compressor.java @@ -28,6 +28,7 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; import org.jackhuang.hellominecraft.utils.functions.Predicate; import java.util.zip.ZipInputStream; +import org.jackhuang.hellominecraft.utils.functions.BiFunction; /** * 文件压缩/解压类 @@ -37,20 +38,21 @@ import java.util.zip.ZipInputStream; public class Compressor { public static void zip(String sourceDir, String zipFile) throws IOException { - zip(new File(sourceDir), new File(zipFile)); + zip(new File(sourceDir), new File(zipFile), null); } /** * 功能:把 sourceDir 目录下的所有文件进行 zip 格式的压缩,保存为指定 zip 文件 * - * @param sourceDir 源文件夹 - * @param zipFile 压缩生成的zip文件路径。 + * @param sourceDir 源文件夹 + * @param zipFile 压缩生成的zip文件路径。 + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName * * @throws java.io.IOException 压缩失败或无法读取 */ - public static void zip(File sourceDir, File zipFile) throws IOException { - FileOutputStream os; - os = new FileOutputStream(zipFile); + public static void zip(File sourceDir, File zipFile, BiFunction pathNameCallback) throws IOException { + FileOutputStream os = new FileOutputStream(zipFile); BufferedOutputStream bos = new BufferedOutputStream(os); try (ZipOutputStream zos = new ZipOutputStream(bos)) { String basePath; @@ -58,22 +60,46 @@ public class Compressor { basePath = sourceDir.getPath(); else//直接压缩单个文件时,取父目录 basePath = sourceDir.getParent(); - zipFile(sourceDir, basePath, zos); + zipFile(sourceDir, basePath, zos, pathNameCallback); zos.closeEntry(); } } + /** + * 功能:把 sourceDir 目录下的所有文件进行 zip 格式的压缩,保存为指定 zip 文件 + * + * @param sourceDir 源文件夹 + * @param zipFile 压缩生成的zip文件路径。 + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName + * + * @throws java.io.IOException 压缩失败或无法读取 + */ + public static ZipOutputStream zipContinuing(File sourceDir, File zipFile, BiFunction pathNameCallback) throws IOException { + FileOutputStream os = new FileOutputStream(zipFile); + BufferedOutputStream bos = new BufferedOutputStream(os); + try (ZipOutputStream zos = new ZipOutputStream(bos)) { + String basePath; + if (sourceDir.isDirectory()) + basePath = sourceDir.getPath(); + else//直接压缩单个文件时,取父目录 + basePath = sourceDir.getParent(); + zipFile(sourceDir, basePath, zos, pathNameCallback); + return zos; + } + } + /** * 将文件压缩成zip文件 * - * @param source zip文件路径 - * @param basePath 待压缩文件根目录 - * @param zos zip文件的os - * - * @param callback if the file is allowed to be zipped. + * @param source zip文件路径 + * @param basePath 待压缩文件根目录 + * @param zos zip文件的os + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName, null if you dont want this file zipped */ private static void zipFile(File source, String basePath, - ZipOutputStream zos) throws IOException { + ZipOutputStream zos, BiFunction pathNameCallback) throws IOException { File[] files; if (source.isDirectory()) files = source.listFiles(); @@ -88,10 +114,18 @@ public class Compressor { if (file.isDirectory()) { pathName = file.getPath().substring(basePath.length() + 1) + "/"; + if (pathNameCallback != null) + pathName = pathNameCallback.apply(pathName, true); + if (pathName == null) + continue; zos.putNextEntry(new ZipEntry(pathName)); - zipFile(file, basePath, zos); + zipFile(file, basePath, zos, pathNameCallback); } else { pathName = file.getPath().substring(basePath.length() + 1); + if (pathNameCallback != null) + pathName = pathNameCallback.apply(pathName, true); + if (pathName == null) + continue; try (InputStream is = new FileInputStream(file)) { BufferedInputStream bis = new BufferedInputStream(is); zos.putNextEntry(new ZipEntry(pathName)); diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/FileUtils.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/FileUtils.java index 6e676f44b..9264b171c 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/FileUtils.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/FileUtils.java @@ -27,7 +27,7 @@ import java.io.OutputStream; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.List; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.NetUtils; /** @@ -321,6 +321,13 @@ public class FileUtils { return filename.substring(index + 1); } + /** + * Get the file name without extensions. + * + * @param filename + * + * @return the file name without extensions + */ public static String getBaseName(String filename) { return removeExtension(getName(filename)); } diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/IOUtils.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/IOUtils.java index 468f51c22..3034e8008 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/IOUtils.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/IOUtils.java @@ -37,7 +37,7 @@ import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; /** * diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/Java.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/Java.java index e7992df6e..2c9f1d77a 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/Java.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/Java.java @@ -23,7 +23,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; /** * diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/JdkVersion.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/JdkVersion.java index 0b5a4a5db..351544db9 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/JdkVersion.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/JdkVersion.java @@ -21,7 +21,7 @@ import java.io.File; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.StrUtils; /** diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/OS.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/OS.java index bc3cc6d56..e43360d99 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/OS.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/OS.java @@ -25,7 +25,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.lang.management.ManagementFactory; import java.util.StringTokenizer; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.StrUtils; /** diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/ProcessThread.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/ProcessThread.java index d1ae491e6..ab2e1a084 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/ProcessThread.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/ProcessThread.java @@ -20,7 +20,7 @@ package org.jackhuang.hellominecraft.utils.system; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.EventHandler; /** diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/ZipEngine.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/ZipEngine.java new file mode 100644 index 000000000..9d3868cdc --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/system/ZipEngine.java @@ -0,0 +1,132 @@ +/* + * Hello Minecraft! Launcher. + * Copyright (C) 2013 huangyuhui + * + * 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 org.jackhuang.hellominecraft.utils.system; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; +import org.jackhuang.hellominecraft.utils.functions.BiFunction; + +/** + * Non thread-safe + * + * @author huangyuhui + */ +public class ZipEngine { + + byte[] buf = new byte[1024]; + ZipOutputStream zos; + + public ZipEngine(File f) throws IOException { + FileOutputStream os = new FileOutputStream(f); + zos = new ZipOutputStream(new BufferedOutputStream(os)); + } + + public void closeFile() throws IOException { + zos.closeEntry(); + zos.close(); + } + + public void putDirectory(String sourceDir) throws IOException { + putDirectory(new File(sourceDir), null); + } + + public void putDirectory(File sourceDir) throws IOException { + putDirectory(sourceDir, null); + } + + /** + * 功能:把 sourceDir 目录下的所有文件进行 zip 格式的压缩,保存为指定 zip 文件 + * + * @param sourceDir 源文件夹 + * @param zipFile 压缩生成的zip文件路径。 + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName + * + * @throws java.io.IOException 压缩失败或无法读取 + */ + public void putDirectory(File sourceDir, BiFunction pathNameCallback) throws IOException { + putDirectoryImpl(sourceDir, sourceDir.isDirectory() ? sourceDir.getPath() : sourceDir.getParent(), pathNameCallback); + } + + /** + * 将文件压缩成zip文件 + * + * @param source zip文件路径 + * @param basePath 待压缩文件根目录 + * @param zos zip文件的os + * @param pathNameCallback callback(pathName, isDirectory) returns your + * modified pathName, null if you dont want this file zipped + */ + private void putDirectoryImpl(File source, String basePath, BiFunction pathNameCallback) throws IOException { + File[] files; + if (source.isDirectory()) + files = source.listFiles(); + else { + files = new File[1]; + files[0] = source; + } + String pathName;//存相对路径(相对于待压缩的根目录) + for (File file : files) + if (file.isDirectory()) { + pathName = file.getPath().substring(basePath.length() + 1) + + "/"; + if (pathNameCallback != null) + pathName = pathNameCallback.apply(pathName, true); + if (pathName == null) + continue; + zos.putNextEntry(new ZipEntry(pathName)); + putDirectoryImpl(file, basePath, pathNameCallback); + } else { + pathName = file.getPath().substring(basePath.length() + 1); + if (pathNameCallback != null) + pathName = pathNameCallback.apply(pathName, true); + if (pathName == null) + continue; + putFile(file, pathName); + } + } + + public void putFile(File file, String pathName) throws IOException { + putStream(new FileInputStream(file), pathName); + } + + public void putStream(InputStream is, String pathName) throws IOException { + int length; + BufferedInputStream bis = new BufferedInputStream(is); + zos.putNextEntry(new ZipEntry(pathName)); + while ((length = bis.read(buf)) > 0) + zos.write(buf, 0, length); + } + + public void putTextFile(String text, String pathName) throws IOException { + putTextFile(text, "UTF-8", pathName); + } + + public void putTextFile(String text, String encoding, String pathName) throws IOException { + putStream(new ByteArrayInputStream(text.getBytes(encoding)), pathName); + } + +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/Task.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/Task.java index c482b4f0a..578329c44 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/Task.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/Task.java @@ -19,7 +19,7 @@ package org.jackhuang.hellominecraft.utils.tasks; import java.util.ArrayList; import java.util.Collection; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; /** * diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/TaskList.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/TaskList.java index 083abb0e9..48882ab32 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/TaskList.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/TaskList.java @@ -24,7 +24,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; /** * diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/TaskWindow.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/TaskWindow.java index d7d9570bb..3a099ec91 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/TaskWindow.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/TaskWindow.java @@ -21,7 +21,7 @@ import java.util.ArrayList; import java.util.LinkedList; import javax.swing.SwingUtilities; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.MessageBox; import org.jackhuang.hellominecraft.utils.StrUtils; import org.jackhuang.hellominecraft.utils.views.SwingUtils; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/download/FileDownloadTask.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/download/FileDownloadTask.java index 1731d6e8b..c708986c8 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/download/FileDownloadTask.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/download/FileDownloadTask.java @@ -25,7 +25,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.tasks.Task; import org.jackhuang.hellominecraft.utils.tasks.communication.PreviousResult; import org.jackhuang.hellominecraft.utils.tasks.communication.PreviousResultRegistrar; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/download/HTTPGetTask.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/download/HTTPGetTask.java index 71baaae15..e6834faeb 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/download/HTTPGetTask.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/tasks/download/HTTPGetTask.java @@ -21,7 +21,7 @@ import java.io.ByteArrayOutputStream; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.tasks.TaskInfo; import org.jackhuang.hellominecraft.utils.tasks.communication.PreviousResult; import org.jackhuang.hellominecraft.utils.EventHandler; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/version/MinecraftVersionRequest.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/version/MinecraftVersionRequest.java index 16892f16e..0f4eedcc3 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/version/MinecraftVersionRequest.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/version/MinecraftVersionRequest.java @@ -22,7 +22,7 @@ import java.io.IOException; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.ArrayUtils; import org.jackhuang.hellominecraft.utils.NetUtils; @@ -151,7 +151,7 @@ public class MinecraftVersionRequest { public static MinecraftVersionRequest minecraftVersion(File file) { MinecraftVersionRequest r = new MinecraftVersionRequest(); - if (!file.exists()) { + if (file == null || !file.exists()) { r.type = MinecraftVersionRequest.NOT_FOUND; return r; } diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/LogWindow.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/LogWindow.java index de5f0ef16..d8652c6d0 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/LogWindow.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/LogWindow.java @@ -23,7 +23,7 @@ import javax.swing.text.Document; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.logging.Level; import org.jackhuang.hellominecraft.utils.functions.NonFunction; import org.jackhuang.hellominecraft.utils.DoubleOutputStream; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/SwingUtils.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/SwingUtils.java index e391381b5..aa37a09dd 100755 --- a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/SwingUtils.java +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/SwingUtils.java @@ -34,7 +34,7 @@ import javax.swing.JTable; import javax.swing.JTextArea; import javax.swing.table.DefaultTableModel; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.MessageBox; import org.jackhuang.hellominecraft.utils.StrUtils; import org.jackhuang.hellominecraft.utils.functions.NonFunction; diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/WizardDisplayer.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/WizardDisplayer.java new file mode 100644 index 000000000..2820cf14d --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/WizardDisplayer.java @@ -0,0 +1,222 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ +package org.jackhuang.hellominecraft.utils.views.wizard.api; + +import java.awt.Container; +import java.awt.Rectangle; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import javax.swing.Action; +import org.jackhuang.hellominecraft.utils.views.wizard.api.displayer.WizardDisplayerImpl; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.Wizard; + +/** + *

Displaying Wizards

+ * Factory which can display a Wizard in a dialog onscreen or in an + * ad-hoc + * container. Usage: + *
+ * Wizard wizard = WizardPage.createWizard (new Class[] {WizardPageSubclass1.class,
+ *     WizardPageSubclass2.class, WizardPageSubclass3.class},
+ *     new MyWizardResultProducer();
+ * WizardDisplayer.showWizard (wizard);
+ * 
+ * Alternately you can implement WizardPanelProvider instead of + * WizardPage to provide the panels of the wizard. + *

+ * To use a Wizard in a JInternalFrame or similar, use + * WizardDisplayer.installInContainer(). You will need to implement + * WizardResultReceiver which will me notified when the wizard + * is finished or cancelled, to close the internal frame or whatever UI is + * showing the wizard. + *

+ *

Customizing the default implementation

+ * The image on the left panel of the default implementation can be customized + * in the following ways: + *
    + *
  • Put an instance of java.awt.image.BufferedImage into + * UIManager with the key wizard.sidebar.image, i.e. + *
    + *    BufferedImage img = ImageIO.read (getClass().getResource ("MySideImage.png");
    + *    UIManager.put ("wizard.sidebar.image", img);
    + * 
    + *
  • + *
  • Use the system property wizard.sidebar.image to set a path + * within a JAR on the classpath to the image. The image must be visible + * to the classloader which loads WizardDisplayer, so this + * may not work in environments which manage the classpath. i.e. + *
    + *    System.setProperty ("wizard.sidebar.image", "com/foo/myapp/MySideImage.png");
    + * 
    + *
  • + *
+ * + *

Providing a custom WizardDisplayer:

+ * The implementation of WizardDisplayer is pluggable. While the + * default implementation should be adequate for most cases, it is possible + * that in some cases one might want to completely replace the UI, buttons, + * etc. with custom UI code. To do that: + *
    + *
  • If the NetBeans Lookup library (org.openide.util.Lookup + * is on the classpath, the default implementation will be found in + * the default lookup (i.e. META-INF/services, same as + * JDK 6's ServiceLoader)
  • + *
  • If Lookup is not available or not found, WizardDisplayer + * will check the system + * property WizardDisplayer.default for a fully qualified + * class name of a subclass of WizardDisplayer. + *
  • + *
  • If no other implementation of WizardDisplayer is found + * by the above methods, the default implementation contained in this + * library will be used.
  • + *
+ * + * @author Tim Boudreau + */ +public abstract class WizardDisplayer { + + protected WizardDisplayer() { + } + private static final String SYSPROP_KEY = "WizardDisplayer.default"; + + /** + * Display a wizard in a dialog, using the default implementation of + * WizardDisplayer. + * + * @param wizard The wizard to show. Must not be null + * @param rect The rectangle on screen for the wizard, may be + * null for default size + * @param help An action to invoke if the user presses the help + * button + * @param initialProperties are the initial values for properties to be + * shown + * and entered in the wizard. May be null. + */ + public static Object showWizard(Wizard wizard, Rectangle rect, Action help, Map initialProperties) { + // assert nonBuggyWizard (wizard); + // validate it + nonBuggyWizard(wizard); + + WizardDisplayer defaultInstance = getDefault(); + + return defaultInstance.show(wizard, rect, help, initialProperties); + } + + private static WizardDisplayer getDefault() { + return new WizardDisplayerImpl(); + } + + /** + * Show a wizard with default window placement and no Help button + */ + public static Object showWizard(Wizard wizard) { + return showWizard(wizard, null, null, null); + } + + /** + * Show a wizard with default window placement, showing the help button, + * which will invoke the passed action. + * + * @param wizard The wizard to show + * @param help An action to invoke if the user presses the help button + * + * @return The result of Wizard.finish() + */ + public static Object showWizard(Wizard wizard, Action help) { + return showWizard(wizard, null, help, null); + } + + /** + * Show a wizard in the passed location on screen with no help button + * + * @param wizard The wizard to show + * @param r The rectangle on screen for the wizard + * + * @return The result of Wizard.finish() + */ + public static Object showWizard(Wizard wizard, Rectangle r) { + return showWizard(wizard, r, null, null); + } + + /** + * Show a wizard. + * + * @param wizard the Wizard to show + * @param r the bounding rectangle for the wizard dialog on + * screen, null means "computed from first panel + * size" + * @param help An action to be called if the Help button is + * pressed + * @param initialProperties are used to set initial values for screens + * within the wizard. + * This may be null. + * + * @return Whatever object the wizard returns from its finish() + * method, if the Wizard was completed by the user. + */ + protected abstract Object show(Wizard wizard, Rectangle r, Action help, Map initialProperties); + + /** + * Install a panel representing a Wizard in a user-supplied container + * with a user-supplied layout constraint. + * + * @param c The container the wizard panel should be added + * to. May not + * be null. + * @param layoutConstraint The argument to use when adding the wizard's + * ui component to the container. May be null. + * @param helpAction An action that should be invoked when the help + * button + * is clicked (if null, no help button will be displayed) + * @param initialProperties A set of properties that should be pre-set upon + * entering the wizard. May be null. + * @param receiver An object which will be called when the Finish + * or + * Cancel buttons are pressed. May not be null. + */ + public static void installInContainer(Container c, Object layoutConstraint, + Wizard awizard, + Action helpAction, Map initialProperties, + WizardResultReceiver receiver) { + getDefault().install(c, layoutConstraint, awizard, helpAction, + initialProperties, receiver); + } + + /** + * Instance implementation of installInContainer(). + */ + protected abstract void install(Container c, Object layoutConstraint, + Wizard awizard, Action helpAction, Map initialProperties, + WizardResultReceiver receiver); + + private static boolean nonBuggyWizard(Wizard wizard) { + String[] s = wizard.getAllSteps(); + // assert new HashSet(Arrays.asList(s)).size() == s.length; + // for JDK 1.4.2: replace assert with runtime exception + if (new HashSet(Arrays.asList(s)).size() != s.length) + throw new RuntimeException("steps are duplicated: " + Arrays.asList(s)); + if (s.length == 1 && Wizard.UNDETERMINED_STEP.equals(s[0])) + // assert false : "Only ID may not be UNDETERMINED_ID"; //NOI18N + throw new RuntimeException("Only ID may not be UNDETERMINED_ID"); + for (int i = 0; i < s.length; i++) + if (Wizard.UNDETERMINED_STEP.equals(s[i]) && i != s.length - 1) + // assert false : "UNDETERMINED_ID may only be last element in" + //NOI18N + // " ids array " + Arrays.asList(s); //NOI18N + throw new RuntimeException("UNDETERMINED_ID may only be last element in" + + //NOI18N + " ids array " + Arrays.asList(s)); //NOI18N) + return true; + } + +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/WizardResultReceiver.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/WizardResultReceiver.java new file mode 100644 index 000000000..37f01ce22 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/WizardResultReceiver.java @@ -0,0 +1,45 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * The Original Software is NetBeans. The Initial Developer of the Original + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun + * Microsystems, Inc. All Rights Reserved. + */ +package org.jackhuang.hellominecraft.utils.views.wizard.api; + +import java.util.Map; + +/** + * Object which is called when the wizard is completed or cancelled. Only + * useful if you want to call WizardDisplayer.installInContainer() to install + * a wizard in a custom container (such as a JInternalDialog) - this class + * is a callback to notify the caller that the Finish or Cancel button has + * been pressed. + * + * @author Tim Boudreau + */ +public interface WizardResultReceiver { + /** + * Called when the wizard has been completed, providing whatever object + * the wizard created as its result. + * @param wizardResult The object created by Wizard.finish() + */ + void finished (Object wizardResult); + /** + * Called when the wizard has been cancelled. + * @param settings The settings that were gathered thus far in the + * wizard + */ + void cancelled (Map settings); +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/InstructionsPanel.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/InstructionsPanel.java new file mode 100644 index 000000000..cf0e6fd35 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/InstructionsPanel.java @@ -0,0 +1,37 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.api.displayer; + +import java.awt.Container; + +/** + * Interface for providing the UI for instructions displayed in a wizard. + * + * @author Tim Boudreau + */ +public interface InstructionsPanel { + /** + * Get the component that will actually display the instructions. + * Note that this component should have a layout manager that can position + * a single component in a location that will not obscure the instructions, + * for showing a progress bar. + * + * This method should always return the same component. + * + * @return A component that can listen to the wizard, display the steps + * in that wizard, notice and update when they change, and optionally + * highlight the current step. + */ + public Container getComponent(); + /** + * Set whether or not the panel is in the summary page at the end of a + * wizard (in which case it should add a "summary" item to its + * list of steps and highlight that). + * + * @param val Whether or not the wizard has navigated to a summary page. + */ + public void setInSummaryPage (boolean val); +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/NavButtonManager.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/NavButtonManager.java new file mode 100644 index 000000000..c5333b3ab --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/NavButtonManager.java @@ -0,0 +1,652 @@ +/* + * NavButtonManager.java created on Dec 9, 2006 + * + */ +package org.jackhuang.hellominecraft.utils.views.wizard.api.displayer; + +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.NoSuchElementException; +import java.util.logging.Logger; + +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.UIManager; + +import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer; +import org.jackhuang.hellominecraft.utils.views.wizard.modules.MergeMap; +import org.jackhuang.hellominecraft.utils.views.wizard.modules.NbBridge; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.DeferredWizardResult; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.Summary; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.Wizard; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardException; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardObserver; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardPage; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardPanel; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardPanelNavResult; + +/** + * Manage the button state and interaction with the wizard. + *

+ * This class is NOT AN API CLASS. There is no + * commitment that it will remain backward compatible or even exist in the + * future. The API of this library is in the packages + * org.netbeans.api.wizard + * and org.netbeans.spi.wizard. + * + * @author stanley@stanleyknutson.com + */ +public class NavButtonManager implements ActionListener { + + static final String NAME_NEXT = "next"; + + static final String NAME_PREV = "prev"; + + static final String NAME_FINISH = "finish"; + + static final String NAME_CANCEL = "cancel"; + + static final String NAME_CLOSE = "close"; + + /** + * Prefix for the name in deferredStatus + */ + static final String DEFERRED_FAILED = "FAILED_"; + + private static final Logger logger + = Logger.getLogger(NavButtonManager.class.getName()); + + JButton next = null; + + JButton prev = null; + + JButton finish = null; + + JButton cancel = null; + + JButton help = null; + + JPanel buttons = null; + + // container can be JDialog or JFrame + private Window window; + + WizardDisplayerImpl parent; + + String closeString = NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Close"); // NOI18N + + boolean suppressMessageDialog = false; + /** + * Deferred status of not null means we are waiting for a deferred result to + * be completed as part of the handling for some button Value of the + * deferredStatus is the NAME_* constant that triggered the deferred + * operation. + */ + String deferredStatus = null; + + NavButtonManager(WizardDisplayerImpl impl) { + parent = impl; + } + + protected void buildButtons(Action helpAction) { + + next = new JButton(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Next_>")); // NOI18N + next.setName(NAME_NEXT); + next.setMnemonic(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Next_mnemonic").charAt(0)); // NOI18N + + prev = new JButton(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "<_Prev")); // NOI18N + prev.setName(NAME_PREV); + prev.setMnemonic(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Prev_mnemonic").charAt(0)); // NOI18N + + finish = new JButton(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Finish")); // NOI18N + finish.setName(NAME_FINISH); + finish.setMnemonic(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Finish_mnemonic").charAt(0)); // NOI18N + + cancel = new JButton(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Cancel")); // NOI18N + cancel.setName(NAME_CANCEL); + cancel.setMnemonic(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Cancel_mnemonic").charAt(0)); // NOI18N + + help = new JButton(); + if (helpAction != null) { + help.setAction(helpAction); + if (helpAction.getValue(Action.NAME) == null) { + help.setText(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Help")); // NOI18N + help.setMnemonic(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Help_mnemonic").charAt(0)); // NOI18N + } + } else { + help.setText(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Help")); // NOI18N + help.setMnemonic(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Help_mnemonic").charAt(0)); // NOI18N + } + + next.setDefaultCapable(true); + prev.setDefaultCapable(true); + + help.setVisible(helpAction != null); + + // Use standard default-button-last order on Aqua L&F + final boolean aqua = "Aqua".equals(UIManager.getLookAndFeel().getID()); // NOI18N + + buttons = new JPanel() { + public void doLayout() { + Insets ins = getInsets(); + JButton b = aqua ? finish : cancel; + + Dimension n = b.getPreferredSize(); + int y = ((getHeight() - (ins.top + ins.bottom)) / 2) - (n.height / 2); + int gap = 5; + int x = getWidth() - (12 + ins.right + n.width); + + b.setBounds(x, y, n.width, n.height); + + b = aqua ? next : finish; + n = b.getPreferredSize(); + x -= n.width + gap; + b.setBounds(x, y, n.width, n.height); + + b = aqua ? prev : next; + n = b.getPreferredSize(); + x -= n.width + gap; + b.setBounds(x, y, n.width, n.height); + + b = aqua ? cancel : prev; + n = b.getPreferredSize(); + x -= n.width + gap; + b.setBounds(x, y, n.width, n.height); + + b = help; + n = b.getPreferredSize(); + x -= n.width + (gap * 2); + b.setBounds(x, y, n.width, n.height); + } + }; + buttons.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, UIManager + .getColor("textText"))); // NOI18N + + buttons.add(prev); + buttons.add(next); + buttons.add(finish); + buttons.add(cancel); + buttons.add(help); + + next.addActionListener(this); + prev.addActionListener(this); + finish.addActionListener(this); + cancel.addActionListener(this); + } + + void connectListener() { + NavWizardObserver l = new NavWizardObserver(); + Wizard wizard = parent.getWizard(); + l.stepsChanged(wizard); + l.navigabilityChanged(wizard); + l.selectionChanged(wizard); + wizard.addWizardObserver(l); + } + + private void configureNavigationButtons(final Wizard wizard, final JButton prev, + final JButton next, final JButton finish) { + final String nextStep = wizard.getNextStep(); + final int fwdNavMode = wizard.getForwardNavigationMode(); + + WizardDisplayerImpl.checkLegalNavMode(fwdNavMode); + + final String problem = wizard.getProblem(); + + final boolean isDeferredResult = deferredStatus != null; + + final boolean canContinue = (fwdNavMode & Wizard.MODE_CAN_CONTINUE) != 0 && !isDeferredResult; + final boolean canFinish = (fwdNavMode & Wizard.MODE_CAN_FINISH) != 0 && !isDeferredResult; + final boolean enableFinish = canFinish && problem == null && !isDeferredResult; + final boolean enableNext = nextStep != null && canContinue && problem == null && !isDeferredResult; + final boolean enablePrevious = wizard.getPreviousStep() != null && !isDeferredResult; + + final Runnable runnable = new Runnable() { + + public void run() { + next.setEnabled(enableNext); + prev.setEnabled(enablePrevious); + finish.setEnabled(enableFinish); + JRootPane root = next.getRootPane(); + if (root != null) + if (next.isEnabled()) + root.setDefaultButton(next); + else if (finish.isEnabled()) + root.setDefaultButton(finish); + else if (prev.isEnabled()) + root.setDefaultButton(prev); + else + root.setDefaultButton(null); + + } + }; + + if (EventQueue.isDispatchThread()) + runnable.run(); + else + EventQueue.invokeLater(runnable); + + } + + public void actionPerformed(ActionEvent event) { + + JButton button = (JButton) event.getSource(); + + String name = button.getName(); + + if (NAME_CANCEL.equals(name)) { + processCancel(event, true); + return; + } + + // probably an error status + if (deferredStatus != null) { + deferredResultFinished(event); + return; + } + + if (NAME_NEXT.equals(name)) + processNext(); + else if (NAME_PREV.equals(name)) + processPrev(); + else if (NAME_FINISH.equals(name)) + processFinish(event); + else if (NAME_CLOSE.equals(name)) + processClose(event); + // else ignore, we don't know it + + parent.updateProblem(); + } + + void deferredResultFailed(final boolean canGoBack) { + final Runnable runnable = new Runnable() { + public void run() { + if (!canGoBack) + getCancel().setText(closeString); + getPrev().setEnabled(true); + getNext().setEnabled(false); + getCancel().setEnabled(true); + getFinish().setEnabled(false); + + if (NAME_CLOSE.equals(deferredStatus)) { + // no action + } else + deferredStatus = DEFERRED_FAILED + deferredStatus; + } + }; + if (EventQueue.isDispatchThread()) + runnable.run(); + else + EventQueue.invokeLater(runnable); + } + + void deferredResultFinished(Object o) { + String name = deferredStatus; + deferredStatus = null; + + configureNavigationButtons(parent.getWizard(), prev, next, finish); + + if (name.startsWith(DEFERRED_FAILED)) { + // Cancel clicked after a deferred failure + if (o instanceof ActionEvent) { + JButton button = (JButton) ((ActionEvent) o).getSource(); + name = button.getName(); + if (NAME_CANCEL.equals(name)) { + processCancel(o instanceof ActionEvent ? (ActionEvent) o + : null, false); + return; + } + } + // in failed state, so we always reload the current step's screen + String currentStep = parent.getCurrentStep(); + parent.navigateTo(currentStep); + return; + } + + if (NAME_NEXT.equals(name)) + processNextProceed(o); + else if (NAME_PREV.equals(name)) + processPrevProceed(o); + else if (NAME_CANCEL.equals(name)) + processCancel(o instanceof ActionEvent ? (ActionEvent) o + : null, false); + else if (NAME_FINISH.equals(name)) + // allowFinish on the "down" click of the finish button + processFinishProceed(o); + else if (NAME_CLOSE.equals(name)) { + // the "up" click of the finish button: wizard.finish was a deferred result + Window dlg = getWindow(); + dlg.setVisible(false); + dlg.dispose(); + } + // else ignore, we don't know it + + parent.updateProblem(); + } + + protected void processNext() { + WizardPanel panel = parent.getCurrentWizardPanel(); + Wizard wizard = parent.getWizard(); + MergeMap settings = parent.getSettings(); + + WizardPanelNavResult proceed = WizardPanelNavResult.PROCEED; + if (panel != null) { + String currentStep = parent.getCurrentStep(); + proceed = panel.allowNext(currentStep, settings, wizard); + if (proceed.isDeferredComputation()) { + deferredStatus = NAME_NEXT; + configureNavigationButtons(wizard, prev, next, finish); + parent.handleDeferredWizardResult(proceed, false); + return; + } + } + + processNextProceed(proceed); + } + + protected void processNextProceed(Object result) { + Wizard wizard = parent.getWizard(); + MergeMap settings = parent.getSettings(); + + if (WizardPanelNavResult.REMAIN_ON_PAGE.equals(result)) + // leave current panel displayed, assume problem is being shown + return; + // ignore other results + + String nextId = wizard.getNextStep(); + settings.push(nextId); + parent.navigateTo(nextId); + parent.setInSummary(false); + } + + protected void processPrev() { + WizardPanel panel = parent.getCurrentWizardPanel(); + Wizard wizard = parent.getWizard(); + MergeMap settings = parent.getSettings(); + + WizardPanelNavResult proceed = WizardPanelNavResult.PROCEED; + if (panel != null) { + String currentStep = parent.getCurrentStep(); + proceed = panel.allowBack(currentStep, settings, wizard); + if (proceed.isDeferredComputation()) { + deferredStatus = NAME_PREV; + parent.handleDeferredWizardResult(proceed, false); + return; + } + } + + processPrevProceed(proceed); + } + + protected void processPrevProceed(Object result) { + Wizard wizard = parent.getWizard(); + MergeMap settings = parent.getSettings(); + + if (WizardPanelNavResult.REMAIN_ON_PAGE.equals(result)) + // leave current panel displayed, assume problem is being shown + return; + // ignore other results + + String prevId = wizard.getPreviousStep(); + settings.popAndCalve(); + parent.setDeferredResult(null); + parent.navigateTo(prevId); + parent.setInSummary(false); + } + + protected void processFinish(ActionEvent event) { + WizardPanel panel = parent.getCurrentWizardPanel(); + Wizard wizard = parent.getWizard(); + MergeMap settings = parent.getSettings(); + + WizardPanelNavResult proceed = WizardPanelNavResult.PROCEED; + if (panel != null) { + String currentStep = parent.getCurrentStep(); + proceed = panel.allowFinish(currentStep, settings, wizard); + if (proceed.isDeferredComputation()) { + deferredStatus = NAME_FINISH; + parent.handleDeferredWizardResult((DeferredWizardResult) proceed, false); + return; + } + } + + processFinishProceed(proceed); + } + + protected void processFinishProceed(Object result) { + Wizard wizard = parent.getWizard(); + MergeMap settings = parent.getSettings(); + + if (WizardPanelNavResult.REMAIN_ON_PAGE.equals(result)) + // leave current panel displayed, assume problem is being shown + return; + try { + Object o = wizard.finish(settings); + // System.err.println("WIZARD FINISH GOT ME A " + o); + + boolean closeWindow = true; + + if (o instanceof DeferredWizardResult) { + final DeferredWizardResult r = (DeferredWizardResult) o; + finish.setEnabled(false); + cancel.setEnabled(r.canAbort()); + prev.setEnabled(false); + next.setEnabled(false); + + // the button still says "cancel" + deferredStatus = NAME_CANCEL; + // deferredStatus = NAME_CLOSE; + parent.handleDeferredWizardResult(r, true); + + closeWindow = false; + } else if (o instanceof Summary) { + parent.handleSummary((Summary) o); + parent.setWizardResult(((Summary) o).getResult()); + // setSummaryShowingMode will be called + // need to share code with NavProgress.finished code path + closeWindow = false; + } else + parent.setWizardResult(o); + + if (closeWindow) + // do cancel processing as well + processCancel(null, false); + } catch (WizardException we) { + if (!suppressMessageDialog) + JOptionPane.showMessageDialog(next, we.getLocalizedMessage()); + String id = we.getStepToReturnTo(); + String curr = settings.currID(); + try { + while (id != null && !id.equals(curr)) + curr = settings.popAndCalve(); + settings.push(id); + parent.navigateTo(id); + return; + } catch (NoSuchElementException ex) { + IllegalStateException e = new IllegalStateException("Exception " + + // NOI18N + "said to return to " + id + " but no such " + + // NOI18N + "step found"); // NOI18N + e.initCause(ex); + throw e; + } + } + } + + protected void processCancel(ActionEvent event, boolean reallyCancel) { + DeferredWizardResult deferredResult = parent.getDeferredResult(); + if (deferredResult != null && deferredResult.canAbort()) + deferredResult.abort(); + Wizard wizard = parent.getWizard(); + MergeMap settings = parent.getSettings(); + + // System.err.println("ProcessCancel " + reallyCancel + " receiver " + parent.receiver); + boolean closeWindow = false; + + if (reallyCancel && parent.cancel()) { + // System.err.println("DO CANCEL"); + logger.fine("calling wizard cancel method on " + wizard); + wizard.cancel(settings); + return; + } + + closeWindow = reallyCancel ? wizard.cancel(settings) : parent.receiver == null; + + // if we have the event (allowFinish was not deferred) then be very sure to close the proper dialog + if (closeWindow) { + Window win = event != null ? (Window) ((JComponent) event.getSource()).getTopLevelAncestor() + : getWindow(); + win.setVisible(false); + win.dispose(); + } + } + + protected void processClose(ActionEvent event) { + Window win = (Window) ((JComponent) event.getSource()).getTopLevelAncestor(); + win.setVisible(false); + win.dispose(); + } + + void updateButtons() { + Wizard wizard = parent.getWizard(); + if (!wizard.isBusy()) + configureNavigationButtons(wizard, prev, next, finish); + } + + void setSummaryShowingMode() { + next.setEnabled(false); + prev.setEnabled(false); + cancel.setEnabled(true); + finish.setEnabled(false); + if (window != null && parent.receiver == null && window instanceof JDialog) + ((JDialog) window).getRootPane().setDefaultButton(cancel); + + cancel.setText(closeString); // NOI18N + cancel.setMnemonic(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle", // NOI18N + WizardDisplayer.class, "Close_mnemonic").charAt(0)); // NOI18N + cancel.setName(NAME_CLOSE); + deferredStatus = null; // ?? should summary be different + } + + void setWindow(Window dlg) { + this.window = dlg; + } + + public JPanel getButtons() { + return buttons; + } + + public JButton getCancel() { + return cancel; + } + + public String getCloseString() { + return closeString; + } + + public Window getWindow() { + return window; + } + + public JButton getFinish() { + return finish; + } + + public JButton getHelp() { + return help; + } + + public JButton getNext() { + return next; + } + + public WizardDisplayerImpl getParent() { + return parent; + } + + public JButton getPrev() { + return prev; + } + + public void initializeNavigation() { + Wizard wizard = parent.getWizard(); + prev.setEnabled(false); + next.setEnabled(wizard.getNextStep() != null); + int fwdNavMode = wizard.getForwardNavigationMode(); + WizardDisplayerImpl.checkLegalNavMode(fwdNavMode); + + finish.setEnabled((fwdNavMode & Wizard.MODE_CAN_FINISH) != 0); + + connectListener(); + } + + // ------------------------------------- + /** + * Listener for wizard changages that affect button state + */ + class NavWizardObserver implements WizardObserver { + + boolean wasBusy = false; + + public void stepsChanged(Wizard wizard) { + // do nothing + } + + public void navigabilityChanged(final Wizard wizard) { + final Runnable runnable = new Runnable() { + + public void run() { + if (wizard.isBusy()) { + next.setEnabled(false); + prev.setEnabled(false); + finish.setEnabled(false); + cancel.setEnabled(false); + parent.getOuterPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + wasBusy = true; + return; + } else if (wasBusy) { + cancel.setEnabled(true); + parent.getOuterPanel().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + } + configureNavigationButtons(wizard, prev, next, finish); + + parent.updateProblem(); + + } + }; + if (EventQueue.isDispatchThread()) + runnable.run(); + else + EventQueue.invokeLater(runnable); + } + + public void selectionChanged(Wizard wizard) { + // do nothing + } + } + +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/NavProgress.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/NavProgress.java new file mode 100644 index 000000000..204c83630 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/NavProgress.java @@ -0,0 +1,204 @@ +package org.jackhuang.hellominecraft.utils.views.wizard.api.displayer; + +import java.awt.Container; +import java.awt.EventQueue; +import java.lang.reflect.InvocationTargetException; +import java.net.URL; +import java.util.logging.Logger; + +import javax.swing.Icon; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JProgressBar; +import javax.swing.border.EmptyBorder; + +import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer; +import org.jackhuang.hellominecraft.utils.views.wizard.modules.InstructionsPanelImpl; +import org.jackhuang.hellominecraft.utils.views.wizard.modules.NbBridge; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.ResultProgressHandle; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.Summary; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardPage; + +/** + * Show progress bar for deferred results, with a label showing percent done and + * progress bar. + * + *

+ * This class is NOT AN API CLASS. There is no + * commitment that it will remain backward compatible or even exist in the + * future. The API of this library is in the packages + * org.netbeans.api.wizard + * and org.netbeans.spi.wizard. + * + * @author stanley@stanleyknutson.com + */ +public class NavProgress implements ResultProgressHandle { + + private static final Logger logger + = Logger.getLogger(NavProgress.class.getName()); + + JProgressBar progressBar = new JProgressBar(); + + JLabel lbl = new JLabel(); + + JLabel busy = new JLabel(); + + WizardDisplayerImpl parent; + + String failMessage = null; + + boolean isUseBusy = false; + + Container ipanel = null; + + boolean isInitialized = false; + + /** + * isRunning is true until finished or failed is called + */ + boolean isRunning = true; + + NavProgress(WizardDisplayerImpl impl, boolean useBusy) { + this.parent = impl; + isUseBusy = useBusy; + } + + public void addProgressComponents(Container panel) { + panel.add(lbl); + if (isUseBusy) { + ensureBusyInitialized(); + panel.add(busy); + } else + panel.add(progressBar); + isInitialized = true; + ipanel = panel; + } + + public void setProgress(final String description, final int currentStep, final int totalSteps) { + Runnable r = new Runnable() { + public void run() { + lbl.setText(description == null ? " " : description); // NOI18N + setProgress(currentStep, totalSteps); + } + }; + invoke(r); + } + + public void setProgress(final int currentStep, final int totalSteps) { + Runnable r = new Runnable() { + public void run() { + if (totalSteps == -1) + progressBar.setIndeterminate(true); + else { + if (currentStep > totalSteps || currentStep < 0) { + if (currentStep == -1 && totalSteps == -1) + return; + throw new IllegalArgumentException("Bad step values: " // NOI18N + + currentStep + " out of " + totalSteps); // NOI18N + } + progressBar.setIndeterminate(false); + progressBar.setMaximum(totalSteps); + progressBar.setValue(currentStep); + } + + setUseBusy(false); + } + }; + invoke(r); + } + + public void setBusy(final String description) { + Runnable r = new Runnable() { + public void run() { + lbl.setText(description == null ? " " : description); // NOI18N + + progressBar.setIndeterminate(true); + + setUseBusy(true); + } + }; + invoke(r); + } + + protected void setUseBusy(boolean useBusy) { + if (isInitialized) + if (useBusy && (!isUseBusy)) { + ipanel.remove(progressBar); + ensureBusyInitialized(); + ipanel.add(busy); + ipanel.invalidate(); + } else if (!useBusy && isUseBusy) { + ipanel.remove(busy); + ipanel.add(progressBar); + ipanel.invalidate(); + } + isUseBusy = useBusy; + } + + private void ensureBusyInitialized() { + if (busy.getIcon() == null) { + URL url = getClass().getResource("busy.gif"); + Icon icon = new ImageIcon(url); + busy.setIcon(icon); + } + } + + private void invoke(Runnable r) { + if (EventQueue.isDispatchThread()) + r.run(); + else + try { + EventQueue.invokeAndWait(r); + } catch (InvocationTargetException ex) { + ex.printStackTrace(); + logger.severe("Error invoking operation " + ex.getClass().getName() + " " + ex.getMessage()); + } catch (InterruptedException ex) { + logger.severe("Error invoking operation " + ex.getClass().getName() + " " + ex.getMessage()); + ex.printStackTrace(); + } + } + + public void finished(final Object o) { + isRunning = false; + Runnable r = new Runnable() { + public void run() { + if (o instanceof Summary) { + Summary summary = (Summary) o; + parent.handleSummary(summary); + parent.setWizardResult(summary.getResult()); + } else if (parent.getDeferredResult() != null) { + parent.setWizardResult(o); + + // handle result based on which button was pushed + parent.getButtonManager().deferredResultFinished(o); + } + } + }; + invoke(r); + } + + public void failed(final String message, final boolean canGoBack) { + failMessage = message; + isRunning = false; + + Runnable r = new Runnable() { + public void run() { + // cheap word wrap + JLabel comp = new JLabel("" + message); // NOI18N + comp.setBorder(new EmptyBorder(5, 5, 5, 5)); + parent.setCurrentWizardPanel(comp); + parent.getTtlLabel().setText( + NbBridge + .getString("org/netbeans/api/wizard/Bundle", // NOI18N + WizardDisplayer.class, "Failed")); // NOI18N + NavButtonManager bm = parent.getButtonManager(); + bm.deferredResultFailed(canGoBack); + } + }; + invoke(r); + } + + public boolean isRunning() { + return isRunning; + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/WizardDisplayerImpl.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/WizardDisplayerImpl.java new file mode 100644 index 000000000..9d91b070b --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/WizardDisplayerImpl.java @@ -0,0 +1,681 @@ +/* The contents of this file are subject to the terms of the Common Development + and Distribution License (the License). You may not use this file except in + compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html + or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file + and include the License file at http://www.netbeans.org/cddl.txt. + If applicable, add the following below the CDDL Header, with the fields + enclosed by brackets [] replaced by your own identifying information: + + Written by Stanley@StanleyKnutson.com based on code from Tim B. + + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.api.displayer; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.ComponentOrientation; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.EventQueue; +import java.awt.Font; +import java.awt.Frame; +import java.awt.KeyboardFocusManager; +import java.awt.Rectangle; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.BorderFactory; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.KeyStroke; +import javax.swing.RootPaneContainer; +import javax.swing.UIManager; +import javax.swing.WindowConstants; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; +import javax.swing.border.EmptyBorder; +import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer; +import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardResultReceiver; +import org.jackhuang.hellominecraft.utils.views.wizard.modules.InstructionsPanelImpl; +import org.jackhuang.hellominecraft.utils.views.wizard.modules.MergeMap; +import org.jackhuang.hellominecraft.utils.views.wizard.modules.NbBridge; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.DeferredWizardResult; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.ResultProgressHandle; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.Summary; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.Wizard; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardPanel; + +/** + * Default implementation of WizardDisplayer. + * This class is NOT AN API CLASS. There is no + * commitment that it will remain backward compatible or even exist in the + * future. The API of this library is in the packages org.netbeans.api.wizard + * and org.netbeans.spi.wizard.

Use + * WizardDisplayer.showWizard() or its other static methods to + * display wizards in a way which will continue to work over time. + * @author stanley@StanleyKnutson.com + * @author Tim Boudreau + */ +public class WizardDisplayerImpl extends WizardDisplayer +{ + + + ResultProgressHandle progress = null; + + JLabel ttlLabel = null; + + JPanel ttlPanel = null; + + Wizard wizard = null; + + JPanel outerPanel = null; + + NavButtonManager buttonManager = null; + + InstructionsPanel instructions = null; + + MergeMap settings = null; + + JPanel inner = null; + + JLabel problem = null; + + Object wizardResult = null; + + WizardResultReceiver receiver = null; + + /** + * WizardPanel is the panel returned as the panel to display. Often a + * subclass of WizardPanel + */ + JComponent wizardPanel = null; + + boolean inSummary = false; + + DeferredWizardResult deferredResult = null; + + /** + * Default constructor used by WizardDisplayer static methods. + * + */ + public WizardDisplayerImpl() + { + } + + protected void buildStepTitle() + { + ttlLabel = new JLabel(wizard.getStepDescription(wizard.getAllSteps()[0])); + ttlLabel.setBorder(BorderFactory.createCompoundBorder(BorderFactory + .createEmptyBorder(5, 5, 12, 5), BorderFactory.createMatteBorder(0, 0, 1, 0, UIManager + .getColor("textText")))); // NOI18N + ttlPanel = new JPanel() + { + public void doLayout() + { + Dimension d = ttlLabel.getPreferredSize(); + if (ttlLabel.getComponentOrientation() == ComponentOrientation.RIGHT_TO_LEFT) + { + ttlLabel.setBounds(getWidth() - d.width, 0, getWidth(), d.height); + } + else + { + ttlLabel.setBounds(0, 0, getWidth(), d.height); + } + } + + public Dimension getPreferredSize() + { + return ttlLabel.getPreferredSize(); + } + }; + ttlPanel.add(ttlLabel); + Font f = ttlLabel.getFont(); + if (f == null) + { + f = UIManager.getFont("controlFont"); // NOI18N + } + if (f != null) + { + f = f.deriveFont(Font.BOLD); + ttlLabel.setFont(f); + } + + } + + /** + * Show a wizard + * + * @param awizard is the wizard to be displayed + * @param bounds for display, may be null for default of 0,0,400,600. + * @param helpAction + * @param initialProperties - initial values for the map + * @return value of the 'finish' processing + * @see org.netbeans.api.wizard.WizardDisplayer#show(org.netbeans.spi.wizard.Wizard, java.awt.Rectangle, javax.swing.Action, java.util.Map) + */ + private JPanel createOuterPanel(final Wizard awizard, Rectangle bounds, Action helpAction, + Map initialProperties) + { + + this.wizard = awizard; + + outerPanel = new JPanel(); + + // apply default size + // we don't enforce any maximum size + if (bounds == null) + { + bounds = new Rectangle(0,0, 600,400); + } + + if (wizard.getAllSteps().length == 0) + { + throw new IllegalArgumentException("Wizard has no steps"); // NOI18N + } + + // initialize the ttl* stuff + buildStepTitle(); + + buttonManager = new NavButtonManager(this); + + outerPanel.setLayout(new BorderLayout()); + + Action kbdCancel = new AbstractAction() { + public void actionPerformed(ActionEvent e) { + JButton b = buttonManager.getCancel(); + if (b.isEnabled()) { + b.doClick(); + } + } + }; + outerPanel.getInputMap(outerPanel.WHEN_IN_FOCUSED_WINDOW).put( + KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); //NOI18N + outerPanel.getActionMap().put("cancel", kbdCancel); //NOI18N + + instructions = createInstructionsPanel(); + + buttonManager.buildButtons(helpAction); + + inner = new JPanel(); + inner.setLayout(new BorderLayout()); + inner.add(ttlPanel, BorderLayout.NORTH); + + problem = new JLabel(" "); + Color fg = UIManager.getColor("nb.errorColor"); // NOI18N + problem.setForeground(fg == null ? Color.BLUE : fg); + inner.add(problem, BorderLayout.SOUTH); + problem.setPreferredSize(new Dimension(20, 20)); + + outerPanel.add(instructions.getComponent(), BorderLayout.WEST); + outerPanel.add(buttonManager.getButtons(), BorderLayout.SOUTH); + outerPanel.add(inner, BorderLayout.CENTER); + + String first = wizard.getAllSteps()[0]; + settings = new MergeMap(first); + + // introduce the initial properties as if they had been set on page 1 + // even though they may be defaults for page 2 + if (initialProperties != null) + { + settings.putAll(initialProperties); + } + + wizardPanel = wizard.navigatingTo(first, settings); + String desc = wizard.getLongDescription (first); + if (desc != null) { + ttlLabel.setText (desc); + } + + inner.add(wizardPanel, BorderLayout.CENTER); + + buttonManager.initializeNavigation(); + return outerPanel; + } + + protected InstructionsPanel createInstructionsPanel() { + return new InstructionsPanelImpl (wizard); + } + + public void install (Container c, Object layoutConstraint, Wizard awizard, + Action helpAction, Map initialProperties, WizardResultReceiver receiver) { + JPanel pnl = createOuterPanel (awizard, new Rectangle(), helpAction, initialProperties); + if (layoutConstraint != null) { + if (c instanceof RootPaneContainer) { + ((RootPaneContainer) c).getContentPane().add (pnl, layoutConstraint); + } else { + c.add (pnl, layoutConstraint); + } + } else { + if (c instanceof RootPaneContainer) { + ((RootPaneContainer) c).getContentPane().add (pnl); + } else { + c.add (pnl); + } + } + this.receiver = receiver; + } + + private static boolean warned; + public Object show(final Wizard awizard, Rectangle bounds, Action helpAction, + Map initialProperties) { + if (!EventQueue.isDispatchThread() && !warned) { + Logger.getLogger(WizardDisplayerImpl.class.getName()).log(Level.WARNING, + "show() should be called from the AWT Event Thread. This " + + "call may deadlock - c.f. " + + "http://java.net/jira/browse/WIZARD-33", new Throwable()); + warned = true; + } + createOuterPanel (awizard, bounds, helpAction, initialProperties); + Object result = showInDialog(bounds); + return result; + } + + protected JDialog createDialog() + { + JDialog dlg; + Object o = findLikelyOwnerWindow(); + if (o instanceof Frame) + { + dlg = new JDialog((Frame) o); + } + else if (o instanceof Dialog) + { + dlg = new JDialog((Dialog) o); + } + else + { + dlg = new JDialog(); + } + return dlg; + } + + protected Object showInDialog(Rectangle bounds) + { + // TODO: add flag for "showInFrame" + + JDialog dlg = createDialog(); + + buttonManager.setWindow(dlg); + + dlg.setTitle(wizard.getTitle()); + + dlg.getContentPane().setLayout(new BorderLayout()); + dlg.getContentPane().add(outerPanel, BorderLayout.CENTER); + if (bounds != null) + { + dlg.setBounds(bounds); + } + else + { + dlg.pack(); + } + dlg.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + dlg.addWindowListener(new WindowAdapter() + { + public void windowClosing(WindowEvent e) + { + if (!(e.getWindow() instanceof JDialog)) { + return; + } + JDialog dlg = (JDialog) e.getWindow(); + boolean dontClose = false; + if (!wizard.isBusy()) + { + DeferredWizardResult defResult; + synchronized(WizardDisplayerImpl.this) { + defResult = deferredResult; + } + try + { + if (defResult != null && defResult.canAbort()) + { + defResult.abort(); + } + else if (defResult != null && !defResult.canAbort()) + { + dontClose = true; + return; + } + } + finally + { + if (!dontClose && wizard.cancel(settings)) + { + dlg.setVisible(false); + dlg.dispose(); + } + } + } + } + }); + + Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); + if (bounds == null) { + // XXX get screen insets? + int x = (d.width - dlg.getWidth()) / 2; + int y = (d.height - dlg.getHeight()) / 2; + dlg.setLocation(x, y); + } + + dlg.setModal(true); + dlg.getRootPane().setDefaultButton(buttonManager.getNext()); + dlg.setVisible(true); + + return wizardResult; + } + + private Window findLikelyOwnerWindow() + { + return KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow(); + } + + /** + * Return the current wizard panel, or null if the currently displayed page + * is not a WizardPanel. + * + * @return + */ + public WizardPanel getCurrentWizardPanel() + { + JComponent comp = wizardPanel; + if (comp instanceof WizardPanel) + { + return (WizardPanel) comp; + } + return null; + } + + public String getCurrentStep() + { + return settings.currID(); + } + + // available in the package only + static void checkLegalNavMode(int i) + { + switch (i) + { + case Wizard.MODE_CAN_CONTINUE: + case Wizard.MODE_CAN_CONTINUE_OR_FINISH: + case Wizard.MODE_CAN_FINISH: + return; + default: + throw new IllegalArgumentException("Illegal forward " + // NOI18N + "navigation mode: " + i); // NOI18N + } + } + + /* + * private static final class LDlg extends JDialog { public LDlg() { + * } public LDlg (Frame frame) { super (frame); } + * + * public LDlg (Dialog dlg) { super (dlg); } + * + * public void setVisible (boolean val) { if (!val) { Thread.dumpStack(); } + * super.setVisible (val); } } + */ + + /** + * Set the currently displayed panel. + * @parm comp is can be anything - it is not required to be a WizardPage or WizardPanel + * */ + public void setCurrentWizardPanel(JComponent comp) + { + inner.add(comp, BorderLayout.CENTER); + inner.remove(wizardPanel); + wizardPanel = comp; + inner.invalidate(); + inner.revalidate(); + inner.repaint(); + comp.requestFocus(); + if (!inSummary) + { + buttonManager.updateButtons(); + } + } + + void handleSummary(Summary summary) + { + inSummary = true; + instructions.setInSummaryPage(true); + JComponent summaryComp = (JComponent) summary.getSummaryComponent(); // XXX + if (summaryComp.getBorder() != null) + { + CompoundBorder b = new CompoundBorder(new EmptyBorder(5, 5, 5, 5), summaryComp + .getBorder()); + summaryComp.setBorder(b); + } + setCurrentWizardPanel((JComponent) summaryComp); // XXX + ttlLabel.setText(NbBridge.getString("org/netbeans/api/wizard/Bundle", // NOI18N + WizardDisplayer.class, "Summary")); // NOI18N + getButtonManager().setSummaryShowingMode(); + summaryComp.requestFocus(); + + } + + protected ResultProgressHandle createProgressDisplay (boolean isUseBusy) + { + return new NavProgress(this, isUseBusy); + } + + void handleDeferredWizardResult(final DeferredWizardResult r, final boolean inSummary) + { + synchronized (this) { + deferredResult = r; + } + wizardPanel.setEnabled(false); + progress = createProgressDisplay(r.isUseBusy()); + Container inst = instructions.getComponent(); + progress.addProgressComponents(inst); + inst.invalidate(); + if (inst instanceof JComponent) { + ((JComponent)inst).revalidate(); + } + inst.repaint(); + Runnable run = new Runnable() + { + public void run() + { + if (!EventQueue.isDispatchThread()) + { + try + { + EventQueue.invokeLater (new Runnable() { + public void run() { + buttonManager.getWindow() + .setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + } + }); + r.start(settings, progress); + if (progress.isRunning()) + { + progress.failed("Start method did not inidicate " + + "failure or finished in " + r, false); + } + + } + finally + { + try + { + EventQueue.invokeAndWait(this); + } + catch (InvocationTargetException ex) + { + ex.printStackTrace(); + return; + } + catch (InterruptedException ex) + { + ex.printStackTrace(); + return; + } + finally + { + buttonManager.getWindow().setCursor(Cursor.getDefaultCursor()); + } + } + } + else + { + synchronized (this) { + deferredResult = null; + } + buttonManager.getCancel().setEnabled(true); + Container inst = instructions.getComponent(); + inst.removeAll(); + inst.invalidate(); + if (inst instanceof JComponent) { + ((JComponent)instructions).revalidate(); + } + inst.repaint(); + } + } + }; + Thread runner = new Thread(run, "Wizard Background Result Thread " + r); // NOI18N + runner.start(); + } + + public void navigateTo(String id) + { + JComponent comp = wizard.navigatingTo(id, getSettings()); + String description = wizard.getLongDescription (id); + if (description == null) { + description = wizard.getStepDescription (id); + } + getTtlLabel().setText(description); + setCurrentWizardPanel(comp); + } + + public NavButtonManager getButtonManager() + { + return buttonManager; + } + + public synchronized DeferredWizardResult getDeferredResult() + { + return deferredResult; + } + + public InstructionsPanel getInstructions() + { + return instructions; + } + + public boolean isInSummary() + { + return inSummary; + } + + public void setInSummary(final boolean state) + { + inSummary = state; + Runnable r = new Runnable() { + public void run() { + instructions.setInSummaryPage(state); + } + }; + if (EventQueue.isDispatchThread()) { + r.run(); + } else { + EventQueue.invokeLater (r); + } + } + + public JPanel getOuterPanel() + { + return outerPanel; + } + + public MergeMap getSettings() + { + return settings; + } + + public JLabel getTtlLabel() + { + return ttlLabel; + } + + public JPanel getTtlPanel() + { + return ttlPanel; + } + + public Wizard getWizard() + { + return wizard; + } + + public JComponent getWizardPanel() + { + return wizardPanel; + } + + public Object getWizardResult() + { + return wizardResult; + } + + public void setWizardResult(Object wizardResult) + { + this.wizardResult = wizardResult; + if (receiver != null) { + receiver.finished(wizardResult); + } + } + + public synchronized void setDeferredResult(DeferredWizardResult deferredResult) + { + this.deferredResult = deferredResult; + } + + /** + * Will only be called if there is a WizardResultReceiver - i.e. if the + * wizard is being displayed in some kind of custom container. Return + * true to indicate we should not try to close the parent window. + */ + boolean cancel() { + boolean result = receiver != null; + if (result) { + receiver.cancelled(settings); + } + return result; + } + + void updateProblem() + { + String prob = wizard.getProblem(); + problem.setText(prob == null ? " " : prob); // NOI18N + if (prob != null && prob.trim().length() == 0) + { + // Issue 3 - provide ability to disable next w/o + // showing the error line + prob = null; + } + Border b = prob == null ? BorderFactory.createEmptyBorder(1, 0, 0, 0) : BorderFactory + .createMatteBorder(1, 0, 0, 0, problem.getForeground()); + + Border b1 = BorderFactory.createCompoundBorder(BorderFactory + .createEmptyBorder(0, 12, 0, 12), b); + + problem.setBorder(b1); + } + +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/InstructionsPanelImpl.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/InstructionsPanelImpl.java new file mode 100644 index 000000000..4327e9633 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/InstructionsPanelImpl.java @@ -0,0 +1,485 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + /* + * InstructionsPanel.java + * + * Created on March 4, 2005, 8:59 PM + */ +package org.jackhuang.hellominecraft.utils.views.wizard.modules; + +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.IllegalComponentStateException; +import java.awt.Insets; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.net.URL; +import java.util.Locale; +import java.util.Arrays; +import javax.accessibility.Accessible; +import javax.accessibility.AccessibleContext; +import javax.accessibility.AccessibleRole; +import javax.accessibility.AccessibleState; +import javax.accessibility.AccessibleStateSet; +import javax.accessibility.AccessibleText; +import javax.imageio.ImageIO; +import javax.swing.CellRendererPane; +import javax.swing.JComponent; +import javax.swing.JEditorPane; +import javax.swing.UIManager; +import org.jackhuang.hellominecraft.utils.views.wizard.api.displayer.InstructionsPanel; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.Wizard; +import org.jackhuang.hellominecraft.utils.views.wizard.spi.WizardObserver; + +/** + * A panel that displays a background image and optionally instructions + * from a wizard, tracking the selected panel and showing that in bold. + *

+ * This class is NOT AN API CLASS. There is no + * commitment that it will remain backward compatible or even exist in the + * future. The API of this library is in the packages + * org.netbeans.api.wizard + * and org.netbeans.spi.wizard. + *

+ * There is currently a single use-case for subclassing this - a navigation + * panel that wants to display a different image for each step. + * + * @author Tim Boudreau + */ +public class InstructionsPanelImpl extends JComponent implements WizardObserver, Accessible, InstructionsPanel { + + private final BufferedImage img; + private final Wizard wizard; + private static final int MARGIN = 5; + + public InstructionsPanelImpl(Wizard wiz) { + this(null, wiz); + Font f = UIManager.getFont("Tree.font"); //NOI18N + if (f != null) + setFont(f); + } + + /** + * Get the wizard this panel is monitoring. + * + * @return + */ + protected final Wizard getWizard() { + return wizard; + } + + public final Container getComponent() { + return this; + } + + /** + * Overridden to start listening to the wizard when added to a container + */ + public void addNotify() { + super.addNotify(); + wizard.addWizardObserver(this); + } + + /** + * Overridden to stop listening to the wizard when removed from a container + */ + public void removeNotify() { + wizard.removeWizardObserver(this); + super.removeNotify(); + } + + /** + * Get the image to be displayed. Note that unpredictable behavior + * may result if all images returned from this method are not the + * same size. Override to display a different wizard depending on the + * step. + * + * @return + */ + protected BufferedImage getImage() { //for unit test + return img; + } + + public InstructionsPanelImpl(BufferedImage img, Wizard wizard) { + if (img == null) + //In the event of classloader issues, also have a way to get + //the image from UIManager - slightly more portable for large + //apps + img = (BufferedImage) UIManager.get("wizard.sidebar.image"); //NOI18N + + String imgStr = System.getProperty("wizard.sidebar.image"); //NOI18N + //image has not been loaded and user wishes to supply their own image + if (img == null && imgStr != null) { + //get an URL, works for jars + ClassLoader cl = this.getClass().getClassLoader(); + URL url = cl.getResource(imgStr); + //successfully parsed the URL + if (url != null) + try { + img = ImageIO.read(url); + } catch (IOException ioe) { + System.err.println("Could not load wizard image " + + //NOI18N + ioe.getMessage()); + System.setProperty("wizard.sidebar.image", null); //NOI18N + img = null; //error loading img, set to null to use default + } + else { //URL was not successfully parsed, set img to null to use default + System.err.println("Bad URL for wizard image " + imgStr); //NOI18N + System.setProperty("wizard.sidebar.image", null); //NOI18N + img = null; + } + } + if (img == null) + try { + img = ImageIO.read(InstructionsPanelImpl.class.getResourceAsStream( + "defaultWizard.png")); //NOI18N + } catch (IOException ioe) { + ioe.printStackTrace(); + } + this.img = img; + this.wizard = wizard; + } + + public boolean isOpaque() { + return img != null; + } + + /** + * Paints the background image for this component, or fills the + * background with a color if no image present. + * + * @param g A Graphic2D to paint into + * @param x The x coordinate of the area that should contain the image + * @param y The y coordinate of the area that should contain the image + * @param w The width of the area that should contain the image + * @param h The height of the area that should contain the image + */ + protected void paintImage(Graphics2D g, int x, int y, int w, int h) { + BufferedImage image = getImage(); + if (image != null) + g.drawImage(image, x, y, w, h, this); + else { + Color c = g.getColor(); + g.setColor(Color.WHITE); + g.fillRect(x, y, w, h); + g.setColor(c); + } + } + + String[] steps = new String[0]; + + public final void paintComponent(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + Font f = getFont() != null ? getFont() : UIManager.getFont("controlFont"); //NOI18N + FontMetrics fm = g.getFontMetrics(f); + Insets ins = getInsets(); + int dx = ins.left; + int dy = ins.top; + int w = getWidth() - (ins.left + ins.right); + int hh = getHeight() - (ins.top + ins.bottom); + paintImage(g2d, dx, dy, w, hh); + String currentStep = wizard.getCurrentStep(); + if (!inSummaryPage) + //Don't fetch step list if in summary page, there will + //only be the base ones + steps = wizard.getAllSteps(); + String steps[] = this.steps; + if (inSummaryPage) { + String summaryStep = NbBridge.getString( + "org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle", //NOI18N + InstructionsPanelImpl.class, "Summary"); //NOI18N + String[] nue = new String[steps.length + 1]; + System.arraycopy(steps, 0, nue, 0, steps.length); + nue[nue.length - 1] = summaryStep; + steps = nue; + } + int y = fm.getMaxAscent() + ins.top + MARGIN; + int x = ins.left + MARGIN; + int h = fm.getMaxAscent() + fm.getMaxDescent() + 3; + + Font boldFont = f.deriveFont(Font.BOLD); + + g.setFont(boldFont); + g.drawString(NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle", //NOI18N + InstructionsPanelImpl.class, "Steps"), x, y); //NOI18N + + int underlineY = ins.top + MARGIN + fm.getAscent() + 3; + g.drawLine(x, underlineY, x + (getWidth() - (x + ins.left + MARGIN)), + underlineY); + + int bottom = getComponentCount() == 0 ? getHeight() - getInsets().bottom + : getHeight() - getInsets().bottom - getComponents()[0].getPreferredSize().height; + + y += h + 10; + int first = 0; + int stop = steps.length; + String elipsis = NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle", //NOI18N + InstructionsPanelImpl.class, "elipsis"); //NOI18N + boolean wontFit = y + (h * (steps.length)) > getHeight(); + if (wontFit) { + //try to center the current step + int availHeight = bottom - y; + int willFit = availHeight / h; + int currStepIndex = Arrays.asList(steps).indexOf(currentStep); + int rangeStart = Math.max(0, currStepIndex - (willFit / 2)); + int rangeEnd = Math.min(rangeStart + willFit, steps.length); + if (rangeStart + willFit > steps.length) { + //Don't scroll off if there's room + rangeStart = steps.length - willFit; + rangeEnd = steps.length; + } + steps = (String[]) steps.clone(); + if (rangeStart != 0) { + steps[rangeStart] = elipsis; + first = rangeStart; + } + if (rangeEnd != steps.length && rangeEnd > 0) { + steps[rangeEnd - 1] = elipsis; + stop = rangeEnd; + } + } + /* + * if (wontFit) { + * int currStepIndex = Arrays.asList (steps).indexOf(currentStep); + * if (currStepIndex != -1) { //shouldn't happen + * steps = (String[]) steps.clone(); + * first = Math.max (0, currStepIndex - 2); + * if (first != 0) { + * if (y + ((currStepIndex - first) * h) > getHeight()) { + * //Best effort to keep current step on screen + * first++; + * } + * if (first != currStepIndex) { + * steps[first] = elipsis; + * } + * } + * } + * } + * if (y + ((stop - first) * h) > bottom) { + * int avail = bottom - y; + * int willFit = avail / h; + * int last = first + willFit - 1; + * if (last < steps.length - 1) { + * steps[last] = elipsis; + * stop = last + 1; + * } + * } + */ + + g.setFont(getFont()); + g.setColor(getForeground()); + + for (int i = first; i < stop; i++) { + boolean isUndetermined = Wizard.UNDETERMINED_STEP.equals(steps[i]); + boolean canOnlyFinish = wizard.getForwardNavigationMode() + == Wizard.MODE_CAN_FINISH; + if (isUndetermined && canOnlyFinish) + break; + String curr; + if (!elipsis.equals(steps[i])) + if (inSummaryPage && i == this.steps.length) + curr = (i + 1) + ". " + steps[i]; + else + curr = (i + 1) + ". " + (isUndetermined + ? NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle", //NOI18N + InstructionsPanelImpl.class, "elipsis") + : //NOI18N + steps[i].equals(elipsis) ? elipsis + : wizard.getStepDescription(steps[i])); //NOI18N + else + curr = elipsis; + if (curr != null) { + boolean selected = (steps[i].equals(currentStep) && !inSummaryPage) + || (inSummaryPage && i == steps.length - 1); + if (selected) + g.setFont(boldFont); + + int width = fm.stringWidth(curr); + while (width > getWidth() - (ins.left + ins.right) && curr.length() > 5) + curr = curr.substring(0, curr.length() - 5) + + NbBridge.getString( + "org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle", //NOI18N + InstructionsPanelImpl.class, "elipsis"); //NOI18N + + g.drawString(curr, x, y); + if (selected) + g.setFont(f); + y += h; + } + } + } + + private int historicWidth = Integer.MIN_VALUE; + + public final Dimension getPreferredSize() { + Font f = getFont() != null ? getFont() + : UIManager.getFont("controlFont"); //NOI18N + + Graphics g = getGraphics(); + if (g == null) + g = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).getGraphics(); + f = f.deriveFont(Font.BOLD); + FontMetrics fm = g.getFontMetrics(f); + Insets ins = getInsets(); + int h = fm.getHeight(); + + String[] steps = wizard.getAllSteps(); + int w = Integer.MIN_VALUE; + for (int i = 0; i < steps.length; i++) { + String desc = i + ". " + (Wizard.UNDETERMINED_STEP.equals(steps[i]) + ? NbBridge.getString("org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle", //NOI18N + InstructionsPanelImpl.class, "elipsis") + : //NOI18N + wizard.getStepDescription(steps[i])); + if (desc != null) + w = Math.max(w, fm.stringWidth(desc) + MARGIN); + } + if (Integer.MIN_VALUE == w) + w = 250; + BufferedImage img = getImage(); + if (img != null) + w = Math.max(w, img.getWidth()); + //Make sure we can grow but not shrink + w = Math.max(w, historicWidth); + historicWidth = w; + return new Dimension(w, ins.top + ins.bottom + ((h + 3) * steps.length)); + } + + private boolean inSummaryPage; + + public void setInSummaryPage(boolean val) { + this.inSummaryPage = val; + repaint(); + } + + public final Dimension getMinimumSize() { + return getPreferredSize(); + } + + public void stepsChanged(Wizard wizard) { + repaint(); + } + + public void navigabilityChanged(Wizard wizard) { + //do nothing + } + + public void selectionChanged(Wizard wizard) { + repaint(); + } + + public final void doLayout() { + Component[] c = getComponents(); + Insets ins = getInsets(); + int y = getHeight() - (MARGIN + ins.bottom); + int x = MARGIN + ins.left; + int w = getWidth() - ((MARGIN * 2) + ins.left + ins.right); + if (w < 0) + w = 0; + for (int i = c.length - 1; i >= 0; i--) { + Dimension d = c[i].getPreferredSize(); + c[i].setBounds(x, y - d.height, w, d.height); + y -= d.height; + } + } + + public final AccessibleContext getAccessibleContext() { + return new ACI(this); + } + + private static final class ACI extends AccessibleContext { + + private final Wizard wizard; + private final InstructionsPanelImpl panel; + + public ACI(InstructionsPanelImpl pnl) { + this.wizard = pnl.wizard; + panel = pnl; + if (pnl.getParent() instanceof Accessible) + setAccessibleParent((Accessible) pnl.getParent()); + setAccessibleName(NbBridge.getString( + "org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle", //NOI18N + InstructionsPanelImpl.class, "ACN_InstructionsPanel")); //NOI18N + setAccessibleDescription(NbBridge.getString( + "org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle", //NOI18N + InstructionsPanelImpl.class, "ACSD_InstructionsPanel")); //NOI18N + } + + JEditorPane pane; + + public AccessibleText getAccessibleText() { + if (pane == null) { + //Cheat just a bit here - will do for now - the text is + //there, more or less where it should be, and a screen + //reader should be able to find it; exact bounds don't + //make much difference + pane = new JEditorPane(); + pane.setBounds(panel.getBounds()); + pane.getAccessibleContext().getAccessibleText(); + pane.setFont(panel.getFont()); + CellRendererPane cell = new CellRendererPane(); + cell.add(pane); + } + pane.setText(getText()); + pane.selectAll(); + pane.validate(); + return pane.getAccessibleContext().getAccessibleText(); + } + + public String getText() { + StringBuffer sb = new StringBuffer(); + String[] s = wizard.getAllSteps(); + for (int i = 0; i < s.length; i++) { + sb.append(wizard.getStepDescription(s[i])); + sb.append('\n'); + } + return sb.toString(); + } + + public AccessibleRole getAccessibleRole() { + return AccessibleRole.LIST; + } + + public AccessibleStateSet getAccessibleStateSet() { + AccessibleState[] states = new AccessibleState[] { + AccessibleState.VISIBLE, + AccessibleState.OPAQUE, + AccessibleState.SHOWING, + AccessibleState.MULTI_LINE, }; + return new AccessibleStateSet(states); + } + + public int getAccessibleIndexInParent() { + return -1; + } + + public int getAccessibleChildrenCount() { + return 0; + } + + public Accessible getAccessibleChild(int i) { + throw new IndexOutOfBoundsException("" + i); + } + + public Locale getLocale() throws IllegalComponentStateException { + return Locale.getDefault(); + } + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/MergeMap.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/MergeMap.java new file mode 100644 index 000000000..d11b6fec1 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/MergeMap.java @@ -0,0 +1,290 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +/* + * MergeMap.java + * + * Created on February 22, 2005, 4:06 PM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.modules; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.Stack; + +/** + * A map which proxies a collection of sub-maps each of which has a + * unique id. Submaps can be added or removed en banc. Values from + * removed maps are retained; if push ("someKnownId") happens, the + * values previously added to the map while that ID was active reappear. + *

+ * This allows us to implement backward/forward semantics for wizards, + * in which each pane (identified with a unique ID) can add its own + * settings to the settings map, but if the user presses the Back + * button, the settings from the formerly active pane can disappear - + * but if the user moves forward again, they are not lost. + *

+ * Calling remove("someKeyBelongingToAnEarlierId") will completely + * remove that value; calling put ("someKeyBelongingToAnEarlierId", "newValue") + * replaces the earler value permanently. + *

+ * This class is NOT AN API CLASS. There is no + * commitment that it will remain backward compatible or even exist in the + * future. The API of this library is in the packages org.netbeans.api.wizard + * and org.netbeans.spi.wizard. + * + * @author Tim Boudreau + */ +public class MergeMap implements Map { + private Stack order = new Stack(); + private Map id2map = new HashMap(); + + /** Creates a new instance of MergeMap */ + public MergeMap(String currID) { + push (currID); + } + + private static final String BASE = "__BASE"; //NOI18N + /** + * Creates a MergeMap with a set of key/value pairs that are + * always there (they came from a legacy wizard - used for bridging the + * old NetBeans wizards API and this one - some bridged wizards will + * have a first panel that gathered some settings using the old APIs + * framework, and we need to inject them here. + */ + public MergeMap(String currId, Map everpresent) { + order.push(BASE); + id2map.put (BASE, everpresent); + push (currId); + } + + /** + * Move to a different ID (meaning add a new named map to proxy which can be + * calved off if necessary). + */ + public Map push (String id) { + // assert !order.contains(id) : id + " already present"; //NOI18N + if (order.contains(id)) { + throw new RuntimeException (id + " already present"); //NOI18N + } +// assert !order.contains(id) : id + " already present"; //NOI18N + if (!order.isEmpty() && id.equals(order.peek())) { + return (Map) id2map.get(id); + } + Map result = (Map) id2map.get(id); + if (result == null) { + result = new HashMap(); + id2map.put (id, result); + } + order.push (id); + return result; + } + + /** + * Get the ID of the current sub-map being written into. + */ + public String currID() { + return (String) order.peek(); + } + + /** + * Remove the current sub-map. Removes all of its settings from the + * MergedMap, but if push() is called with the returned value, the + * values associated with the ID being removed will be restored. + */ + public String popAndCalve() { + if (order.size() == 0) { + throw new NoSuchElementException ("Cannot back out past first " + //NOI18N + "entry"); //NOI18N + } + //Get the current map + String result = (String) order.peek(); + Map curr = (Map) id2map.get (result); + order.pop(); + + //Though unlikely, it is possible that a later step in a wizard + //overwrote a key/value pair from a previous step of the wizard. + //We do not want to revert that write, so iterate all the keys + //we're removing, and if any of them are in steps lower on the + //stack, change those lower steps values to whatever was written + //into the map we're calving off + + Set keysForCurr = curr.keySet(); + for (Iterator i=orderIterator(); i.hasNext();) { + Map other = (Map) id2map.get(i.next()); + for (Iterator j=curr.keySet().iterator(); j.hasNext();) { + Object key = j.next(); + if (other.containsKey(key)) { + other.put (key, curr.get(key)); + } + } + } + return result; + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public boolean containsKey(Object obj) { + for (Iterator i = orderIterator(); i.hasNext();) { + Map curr = (Map) id2map.get(i.next()); + if (curr.containsKey(obj)) { + return true; + } + } + return false; + } + + public boolean containsValue(Object obj) { + for (Iterator i = orderIterator(); i.hasNext();) { + Map curr = (Map) id2map.get(i.next()); + if (curr.containsValue(obj)) { + return true; + } + } + return false; + } + + public java.util.Set entrySet() { + HashSet result = new HashSet(); + for (Iterator i = orderIterator(); i.hasNext();) { + Map curr = (Map) id2map.get(i.next()); + result.addAll (curr.entrySet()); + } + return result; + } + + public Object get(Object obj) { + for (Iterator i = orderIterator(); i.hasNext();) { + String id = (String) i.next(); + Map curr = (Map) id2map.get(id); + Object result = curr.get(obj); + if (result != null) { + return result; + } + } + return null; + } + + public boolean isEmpty() { + return size() == 0; + } + + public Set keySet() { + HashSet result = new HashSet(); + for (Iterator i = orderIterator(); i.hasNext();) { + Map curr = (Map) id2map.get(i.next()); + result.addAll (curr.keySet()); + } + return result; + } + + public Object put(Object obj, Object obj1) { + Map curr = (Map) id2map.get (order.peek()); + return curr.put (obj, obj1); + } + + public void putAll(Map map) { + Map curr = (Map) id2map.get (order.peek()); + curr.putAll (map); + } + + private Object doRemove(Object obj) { + Map curr = (Map) id2map.get (order.peek()); + Object result = curr.remove (obj); + if (result == null) { + for (Iterator i = orderIterator(); i.hasNext();) { + curr = (Map) id2map.get(i.next()); + result = curr.remove (obj); + if (result != null) { + break; + } + } + } + return result; + } + + public Object remove(Object obj) { + //Ensure we remove any duplicates in upper arrays + Object result = get(obj); + while (get(obj) != null) { + doRemove (obj); + } + return result; + } + + public int size() { + //using keySet() prunes duplicates + return keySet().size(); + } + + public Collection values() { + HashSet result = new HashSet(); + Set keys = keySet(); + for (Iterator i = keys.iterator(); i.hasNext();) { + result.add (get(i.next())); + } + return result; + } + + private Iterator orderIterator() { + return new ReverseIterator(order); + } + + private static final class ReverseIterator implements Iterator { + private int pos; + private List l; + public ReverseIterator (Stack s) { + pos = s.size()-1; + l = new ArrayList(s); + } + + public boolean hasNext() { + return pos != -1; + } + + public Object next() { + if (pos < 0) { + throw new NoSuchElementException(); + } + Object result = l.get(pos); + pos--; + return result; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + for (Iterator i = keySet().iterator(); i.hasNext();) { + Object key = (Object) i.next(); + sb.append ('['); + sb.append (key); + sb.append('='); + sb.append(get(key)); + sb.append(']'); + if (i.hasNext()) sb.append (','); + } + return sb.toString(); + } + +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/NbBridge.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/NbBridge.java new file mode 100644 index 000000000..e243918f6 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/modules/NbBridge.java @@ -0,0 +1,34 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ +package org.jackhuang.hellominecraft.utils.views.wizard.modules; + +import java.util.ResourceBundle; + +/** + * Non API class for accessing a few things in NetBeans via reflection. + * + * @author Tim Boudreau + */ +public final class NbBridge { + + private NbBridge() { + } + + public static String getString(String path, Class callerType, String key) { + return getStringViaResourceBundle(path, key); + } + + private static String getStringViaResourceBundle(String path, String key) { + return ResourceBundle.getBundle(path).getString(key); + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/BranchingWizard.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/BranchingWizard.java new file mode 100644 index 000000000..be142b175 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/BranchingWizard.java @@ -0,0 +1,349 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ + +/* + * BranchingWizard.java + * + * Created on March 4, 2005, 10:56 PM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import javax.swing.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * A Wizard with indeterminate branches. The actual branch decision-making + * is done by the WizardBranchController passed to the constructor. + *

+ * Wizards with arbitrary numbers of branches can be handled by a + * WizardBranchController by returning wizards created by + * another WizardBranchController's createWizard() method. + *

+ * One important point: There should be no duplicate IDs between steps of + * this wizard. + * + * @author Tim Boudreau + */ +final class BranchingWizard implements WizardImplementation { + private final List listenerList = Collections.synchronizedList ( + new LinkedList()); + + private final WizardBranchController brancher; + final WizardImplementation initialSteps; + + private WizardImplementation subsequentSteps; + private WizardImplementation activeWizard; + private WL wl; + + private String currStep; + private Map wizardData; + + public BranchingWizard(WizardBranchController brancher) { + this.brancher = brancher; + initialSteps = new SimpleWizard(brancher.getBase(), true); + setCurrent(initialSteps); + } + + protected final WizardImplementation createSecondary(Map settings) { + Wizard wiz = brancher.getWizardForStep(currStep, settings); + return wiz == null ? null : wiz.impl; + } + + private void checkForSecondary() { + if (wizardData == null) { + return; + } + + WizardImplementation newSecondary = createSecondary(wizardData); + + /* + * johnflournoy 7/20/07 + * check for secondary should be adding the secondary to the activeWizard + * not the initial wizard. Adding it to the initial wizard was breaking + * multiple branching - to accomplish this created a new method: + * setSecondary() + */ + if (activeWizard instanceof BranchingWizard) { + ((BranchingWizard) activeWizard).setSecondary(newSecondary); + } else { + this.setSecondary(newSecondary); + } + } + + /** + * Set the secondary for this BranchingWizard. + * @param newSecondary is a WizardImplementation. + */ + private void setSecondary(WizardImplementation newSecondary) { + /* johnflournoy added additional condition: secondary != this */ + if ((((subsequentSteps == null) != (newSecondary == null)) + || (subsequentSteps != null && !subsequentSteps.equals(newSecondary))) + && !this.equals(newSecondary)) { + + /* + * johnflournoy: only set the subsequent steps if it + * this wizard owns the current step. + */ + if (Arrays.asList(initialSteps.getAllSteps()).contains(currStep)) { + subsequentSteps = newSecondary; + fireStepsChanged(); + } + } + } + + + public int getForwardNavigationMode() { + return activeWizard.getForwardNavigationMode(); + } + + private void setCurrent(WizardImplementation wizard) { + if (activeWizard == wizard) { + return; + } + + if (wizard == null) { + throw new NullPointerException("Can't set current wizard to null"); + } + + if ((activeWizard != null) && (wl != null)) { + activeWizard.removeWizardObserver(wl); + } + + activeWizard = wizard; + + if (wl == null) { + wl = new WL(); + } + + activeWizard.addWizardObserver(wl); + } + + public final boolean isBusy() { + return activeWizard.isBusy(); + } + + public final Object finish(Map settings) throws WizardException { + try { + Object result = activeWizard.finish(settings); + initialSteps.removeWizardObserver(wl); + //Can be null, we allow bail-out with finish mid-wizard now + if (subsequentSteps != null) { + subsequentSteps.removeWizardObserver(wl); + } + return result; + } catch (WizardException we) { + if (we.getStepToReturnTo() != null) { + initialSteps.addWizardObserver(wl); + //Can be null, we allow bail-out with finish mid-wizard now + if (subsequentSteps != null) { + subsequentSteps.addWizardObserver(wl); + } + } + throw we; + } + } + + public final String[] getAllSteps() { + String[] result; + if (subsequentSteps == null) { + String[] bsteps = initialSteps.getAllSteps(); + result = new String[bsteps.length + 1]; + System.arraycopy(bsteps, 0, result, 0, bsteps.length); + result[result.length - 1] = UNDETERMINED_STEP; + } else { + String[] bsteps = initialSteps.getAllSteps(); + String[] csteps = subsequentSteps.getAllSteps(); + result = new String[bsteps.length + csteps.length]; + System.arraycopy(bsteps, 0, result, 0, bsteps.length); + System.arraycopy(csteps, 0, result, bsteps.length, csteps.length); + } + return result; + } + + public String getCurrentStep() { + return currStep; + } + + public final String getNextStep() { + String result; + if (currStep == null) { + result = getAllSteps()[0]; + } else { + String[] steps = getAllSteps(); + int idx = Arrays.asList(steps).indexOf(currStep); + if (idx == -1) { + throw new IllegalStateException("Current step not in" + //NOI18N + " available steps: " + currStep + " not in " + //NOI18N + Arrays.asList(steps)); + } else { + if (idx == steps.length - 1) { + if (subsequentSteps == null) { + result = UNDETERMINED_STEP; + } else { + result = subsequentSteps.getNextStep(); + } + } else { + WizardImplementation w = ownerOf(currStep); + if (w == initialSteps && idx == initialSteps.getAllSteps().length - 1) { + checkForSecondary(); + if (subsequentSteps != null) { + result = subsequentSteps.getAllSteps()[0]; + } else { + result = UNDETERMINED_STEP; + } + } else { + result = w.getNextStep(); + } + } + } + } + return getProblem() == null ? result : UNDETERMINED_STEP.equals(result) ? result : null; + } + + public final String getPreviousStep() { + if (activeWizard == subsequentSteps && subsequentSteps.getAllSteps()[0].equals(currStep)) { + return initialSteps.getAllSteps()[initialSteps.getAllSteps().length - 1]; + } else { + return activeWizard.getPreviousStep(); + } + } + + public final String getProblem() { + return activeWizard.getProblem(); + } + + public final String getStepDescription(String id) { + WizardImplementation w = ownerOf(id); + if (w == null) { + return null; + } + return w.getStepDescription(id); + } + + public final String getLongDescription(String id) { + WizardImplementation w = ownerOf(id); + if (w == null) { + return null; + } + return w.getLongDescription(id); + } + + private WizardImplementation ownerOf(String id) { + if (UNDETERMINED_STEP.equals(id)) { + checkForSecondary(); + return subsequentSteps; + } + if (Arrays.asList(initialSteps.getAllSteps()).contains(id)) { + return initialSteps; + } else { + /* + * johnflournoy + * need to check an existing subsequentsteps to see if + * we can find the owner of "id", otherwise we were losing + * a wizard if we had multiple branches and we backed up to an + * earlier wizard and then went down the same path again. + */ + if (subsequentSteps != null) { + if (!Arrays.asList(subsequentSteps.getAllSteps()).contains(id)) { + checkForSecondary(); + } + } else { + checkForSecondary(); + } + + return subsequentSteps; + } + } + + public final String getTitle() { + return activeWizard.getTitle(); + } + + public final JComponent navigatingTo(String id, Map settings) { + if (id == null) { + throw new NullPointerException(); + } + currStep = id; + wizardData = settings; + + WizardImplementation impl = ownerOf (id); + if (impl == null) { + throw new NullPointerException ("No owning WizardImplementation for" + + " id " + id); + } + setCurrent(impl); + + return activeWizard.navigatingTo(id, settings); + } + + public final void removeWizardObserver (WizardObserver observer) { + listenerList.remove(observer); + } + + public final void addWizardObserver (WizardObserver observer) { + listenerList.add(observer); + } + + private void fireStepsChanged() { + WizardObserver[] listeners = (WizardObserver[]) + listenerList.toArray (new WizardObserver[0]); + + for (int i = listeners.length - 1; i >= 0; i --) { + WizardObserver l = (WizardObserver) listeners[i]; + l.stepsChanged(null); + } + } + + private void fireNavigabilityChanged() { + checkForSecondary(); + + WizardObserver[] listeners = (WizardObserver[]) + listenerList.toArray (new WizardObserver[0]); + + for (int i = listeners.length - 1; i >= 0; i --) { + WizardObserver l = (WizardObserver) listeners[i]; + l.navigabilityChanged(null); + } + } + + private void fireSelectionChanged() { + WizardObserver[] listeners = (WizardObserver[]) + listenerList.toArray (new WizardObserver[0]); + + for (int i = listeners.length - 1; i >= 0; i --) { + WizardObserver l = (WizardObserver) listeners[i]; + l.selectionChanged(null); + } + } + + public boolean cancel(Map settings) { + return activeWizard == null ? true : activeWizard.cancel(settings); + } + + private class WL implements WizardObserver { + public void stepsChanged(Wizard wizard) { + fireStepsChanged(); + } + + public void navigabilityChanged(Wizard wizard) { + fireNavigabilityChanged(); + } + + public void selectionChanged(Wizard wizard) { + fireSelectionChanged(); + } + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/DeferredWizardResult.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/DeferredWizardResult.java new file mode 100644 index 000000000..da41cd1b7 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/DeferredWizardResult.java @@ -0,0 +1,152 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ + +/* + * DeferredWizardResult.java + * + * Created on September 24, 2006, 3:42 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.util.Map; + + +/** + * Object which can be returned from + * WizardPage.WizardResultProducer.finish() + * or WizardPanelProvider.finish(). A DeferredWizardResult does + * not immediately calculate its result; it is used for cases where some + * time consuming work needs to be performed to compute the result (such as + * creating files on disk), and a progress bar should be shown until the work + * is completed. + * @see org.jackhuang.hellominecraft.utils.views.wizard.spi.ResultProgressHandle + * + * @author Tim Boudreau + */ +public abstract class DeferredWizardResult { + private final boolean canAbort; + private final boolean useBusy; + /** + * Creates a new instance of DeferredWizardResult which cannot be + * aborted and shows a progress bar. + */ + public DeferredWizardResult() { + useBusy = false; + canAbort = false; + } + + /** Creates a new instance of DeferredWizardResult which may or may not + * be able to be aborted. + * @param canAbort determine if background computation can be aborted by + * calling the abort() method + */ + public DeferredWizardResult (boolean canAbort) { + this.canAbort = canAbort; + this.useBusy = false; + } + + /** Creates a new instance of DeferredWizardResult which may or may not + * be able to be aborted, and which may simply disable the wizard's UI + * instead of showing a progress bar while the background work runs. + * + * @param canAbort + * @param useBusy + */ + public DeferredWizardResult (boolean canAbort, boolean useBusy) { + this.canAbort = canAbort; + this.useBusy = useBusy; + } + + + /** + * Begin computing the result. This method is called on a background + * thread, not the AWT event thread, and computation can immediately begin. + * Use the progress handle to set progress as the work progresses. + * + * IMPORTANT: This method MUST call either progress.finished with the result, + * or progress.failed with an error message. If this method returns without + * calling either of those methods, it will be assumed to have failed. + * + * @param settings The settings gathered over the course of the wizard + * @param progress A handle which can be used to affect the progress bar. + */ + public abstract void start (Map settings, ResultProgressHandle progress); + + /** + * If true, the background thread can be aborted. If it is possible to + * abort, then the UI may allow the dialog to be closed while the result + * is being computed. + */ + public final boolean canAbort() { + return canAbort; + } + + /** + * Abort computation of the result. This method will usually be called on + * the event thread, after start() has been called, and before + * finished() has been called on the ResultProgressHandle + * that is passed to start(), for example, if the user clicks + * the close button on the dialog showing the wizard while the result is + * being computed. + *

+ * This method does nothing by default - it is left empty so + * that people who do not want to support aborting background work do not + * have to override it. It is up to the implementor + * to set a flag or otherwise notify the background thread to halt + * computation. A simple method for doing so is as follows: + *

+     * volatile Thread thread;
+     * public void start (Map settings, ResultProgressHandle handle) {
+     * try {
+     *  synchronized (this) {
+     *     thread = Thread.currentThread();
+     *  }
+     *  
+     *  //do the background computation, update progress.  Every so often, 
+     *  //check Thread.interrupted() and exit if true
+     * } finally {
+     *    synchronized (this) {
+     *       thread = null;
+     *    }
+     *  }
+     * }
+     * 
+     * public synchronized void abort() {
+     *  if (thread != null) thread.interrupt();
+     * }
+     * 
+ * or you can use a volatile boolean flag that you set in + * abort() and periodically check in the body of start(). + * + */ + public void abort() { + //do nothing + } + + /** + * Determine if the UI should be completely disabled while the background + * work is running (i.e. you do not want a progress bar, you just want all + * navigation disabled [note on some window managers, the user will still + * be able to click the dialog's window drag-bar close button, so you still + * should override abort() to stop computation if possible]). + * + * @return true if no progress bar should be displayed and the UI should + * just disable itself + */ + public final boolean isUseBusy() + { + return useBusy; + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/GenericListener.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/GenericListener.java new file mode 100644 index 000000000..292475df4 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/GenericListener.java @@ -0,0 +1,439 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ + +/* + * GenericListener.java + * + * Created on October 5, 2004, 12:36 AM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dialog; +import java.awt.Frame; +import java.awt.Window; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.Arrays; +import java.util.EventObject; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.logging.Logger; +import java.util.logging.Level; +import javax.swing.*; +import javax.swing.table.*; +import javax.swing.tree.*; +import javax.swing.ListSelectionModel; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.text.Document; +import javax.swing.text.JTextComponent; +import javax.swing.tree.TreeSelectionModel; + +/** + * A listener that can listen to just about any standard swing component + * that accepts user input and notify the panel that it needs to + * validate its contents. + * + * If you use subclasses of the swing components, you will also need to subclass + * this listener and override at least the methods isProbablyContainer, attachTo and detachFrom. + * + * @author Tim Boudreau + */ +final class GenericListener + implements ActionListener, PropertyChangeListener, ItemListener, + ContainerListener, DocumentListener, ChangeListener, + ListSelectionListener, TreeSelectionListener, TableModelListener { + + private static final Logger logger = + Logger.getLogger(GenericListener.class.getName()); + + private final WizardPage wizardPage; + + private boolean ignoreEvents; + + /** + * Set of components that we're listening to models of, so we can look + * up the component from the model as needed + */ + private Set listenedTo = new HashSet(); + + private final WizardPage.CustomComponentListener extListener; + private final WizardPage.CustomComponentNotifier extNotifier; + public GenericListener(WizardPage wizardPage, WizardPage.CustomComponentListener l, + WizardPage.CustomComponentNotifier n) { + this.extListener = l; + this.extNotifier = n; + if ((extListener == null) != (extNotifier == null)) { + throw new RuntimeException(); + } + // assert wizardPage != null : "WizardPage may not be null"; // NOI18N + if (wizardPage == null) { + throw new IllegalArgumentException("WizardPage may not be null"); // NOI18N) + } + this.wizardPage = wizardPage; + wizardPage.addContainerListener(this); + } + + public GenericListener (WizardPage page) { + this (page, null, null); + } + + /** + * Return true if the given component is likely to be a container such the each + * component within the container should be be considered as a user input. + * + * @param c + * @return true if the component children should have this listener added. + */ + protected boolean isProbablyAContainer (Component c) { + boolean result = extListener != null ? extListener.isContainer(c) : false; + if (!result) { + boolean isSwing = isSwingClass(c); + if (isSwing) { + result = c instanceof JPanel || c instanceof JSplitPane || c instanceof + JToolBar || c instanceof JViewport || c instanceof JScrollPane || + c instanceof JFrame || c instanceof JRootPane || c instanceof + Window || c instanceof Frame || c instanceof Dialog || + c instanceof JTabbedPane || c instanceof JInternalFrame || + c instanceof JDesktopPane || c instanceof JLayeredPane || + c instanceof Box; + } else { + result = c instanceof Container; + } + } + return result; + } + + /** + * Return true if the given component is likely to be a swing primitive or a subclass. + * The default implmentation here just checks for the package of the component to be "javax.swing" + * If you use subclasses of swing components, you will need to override this method + * to get proper behavior. + * + * @param c + * @return true if the component should be examined more closely (see isProbablyAContainer) + */ + protected boolean isSwingClass (Component c) + { + String packageName = c.getClass().getPackage().getName(); + boolean swing = packageName.equals ("javax.swing"); //NOI18N + return swing; + } + + protected void attachTo(Component jc) { + if (extListener != null && extListener.accept (jc)) { + extListener.startListeningTo(jc, extNotifier); + listenedTo.add (jc); + if (wizardPage.getMapKeyFor(jc) != null) { + wizardPage.maybeUpdateMap(jc); + } + return; + } + if (isProbablyAContainer(jc)) { + attachToHierarchyOf((Container) jc); + } else if (jc instanceof JList) { + listenedTo.add(jc); + ((JList) jc).addListSelectionListener(this); + } else if (jc instanceof JComboBox) { + ((JComboBox) jc).addActionListener(this); + } else if (jc instanceof JTree) { + listenedTo.add(jc); + ((JTree) jc).getSelectionModel().addTreeSelectionListener(this); + } else if (jc instanceof JToggleButton) { + ((AbstractButton) jc).addItemListener(this); + } else if (jc instanceof JFormattedTextField) { //JFormattedTextField must be tested before JTextCompoent + jc.addPropertyChangeListener("value", this); + } else if (jc instanceof JTextComponent) { + listenedTo.add(jc); + ((JTextComponent) jc).getDocument().addDocumentListener(this); + } else if (jc instanceof JColorChooser) { + listenedTo.add(jc); + ((JColorChooser) jc).getSelectionModel().addChangeListener(this); + } else if (jc instanceof JSpinner) { + ((JSpinner) jc).addChangeListener(this); + } else if (jc instanceof JSlider) { + ((JSlider) jc).addChangeListener(this); + } else if (jc instanceof JTable) { + listenedTo.add(jc); + ((JTable) jc).getSelectionModel().addListSelectionListener(this); + } else { + if (logger.isLoggable(Level.FINE)) { + logger.fine("Don't know how to listen to a " + // NOI18N + jc.getClass().getName()); + } + } + + if (accept(jc) && !(jc instanceof JPanel)) { + jc.addPropertyChangeListener("name", this); + if (wizardPage.getMapKeyFor(jc) != null) { + wizardPage.maybeUpdateMap(jc); + } + } + + if (logger.isLoggable(Level.FINE) && accept(jc)) { + logger.fine("Begin listening to " + jc); // NOI18N + } + } + + protected void detachFrom(Component jc) { + listenedTo.remove(jc); + if (extListener != null && extListener.accept (jc)) { + extListener.stopListeningTo(jc); + } + if (isProbablyAContainer(jc)) { + detachFromHierarchyOf((Container) jc); + } else if (jc instanceof JList) { + ((JList) jc).removeListSelectionListener(this); + } else if (jc instanceof JComboBox) { + ((JComboBox) jc).removeActionListener(this); + } else if (jc instanceof JTree) { + ((JTree) jc).getSelectionModel().removeTreeSelectionListener(this); + } else if (jc instanceof JToggleButton) { + ((AbstractButton) jc).removeActionListener(this); + } else if (jc instanceof JTextComponent) { + } else if (jc instanceof JFormattedTextField) { //JFormattedTextField must be tested before JTextCompoent + jc.removePropertyChangeListener("value", this); + ((JTextComponent) jc).getDocument().removeDocumentListener(this); + } else if (jc instanceof JColorChooser) { + ((JColorChooser) jc).getSelectionModel().removeChangeListener(this); + } else if (jc instanceof JSpinner) { + ((JSpinner) jc).removeChangeListener(this); + } else if (jc instanceof JSlider) { + ((JSlider) jc).removeChangeListener(this); + } else if (jc instanceof JTable) { + ((JTable) jc).getSelectionModel().removeListSelectionListener(this); + } + + if (accept(jc) && !(jc instanceof JPanel)) { + jc.removePropertyChangeListener("name", this); + Object key = wizardPage.getMapKeyFor(jc); + + if (key != null) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("Named component removed from hierarchy: " + // NOI18N + key + ". Removing any corresponding " + // NOI18N + "value from the wizard settings map."); // NOI18N + } + + wizardPage.removeFromMap(key); + } + } + + if (logger.isLoggable(Level.FINE) && accept(jc)) { + logger.fine("Stop listening to " + jc); // NOI18N + } + } + + private void detachFromHierarchyOf(Container container) { + container.removeContainerListener(this); + Component[] components = container.getComponents(); + for (int i = 0; i < components.length; i++) { + detachFrom(components[i]); // Will callback recursively any nested JPanels + } + } + + void attachToHierarchyOf(Container container) { + if (!Arrays.asList (container.getContainerListeners()).contains(this)) { + container.addContainerListener(this); + } + Component[] components = container.getComponents(); + for (int i = 0; i < components.length; i++) { + attachTo(components[i]); // Will recursively add any child components in + } + } + + protected boolean accept(Component jc) { + if (extListener != null && extListener.accept(jc)) { + return true; + } + if (!(jc instanceof JComponent)) { + return false; + } + if (jc instanceof TableCellEditor || jc instanceof TreeCellEditor || + SwingUtilities.getAncestorOfClass(JTable.class, jc) != null || + SwingUtilities.getAncestorOfClass(JTree.class, jc) != null || + SwingUtilities.getAncestorOfClass(JList.class, jc) != null){ + //Don't listen to cell editors, we can end up listening to them + //multiple times, and the tree/table model will give us the event + //we need + return false; + } + return isProbablyAContainer (jc) || + jc instanceof JList || + jc instanceof JComboBox || + jc instanceof JTree || + jc instanceof JToggleButton || //covers toggle, radio, checkbox + jc instanceof JTextComponent || + jc instanceof JColorChooser || + jc instanceof JSpinner || + jc instanceof JSlider; + } + + void setIgnoreEvents(boolean val) { + ignoreEvents = val; + } + + private void fire(Object e) { + if (!ignoreEvents) { + setIgnoreEvents(true); + try { + //XXX this could be prettier... + if (logger.isLoggable(Level.FINE)) { + logger.fine("Event received: " + e); // NOI18N + } + if (e instanceof EventObject && ((EventObject) e).getSource() instanceof Component) { + wizardPage.userInputReceived((Component) ((EventObject) e).getSource(), e); + } else if (e instanceof TreeSelectionEvent) { + logger.fine("Looking for a tree for a tree selection event"); // NOI18N + TreeSelectionModel mdl = (TreeSelectionModel) ((TreeSelectionEvent) e).getSource(); + for (Iterator i = listenedTo.iterator(); i.hasNext();) { + Object o = i.next(); + if (o instanceof JTree && ((JTree) o).getSelectionModel() == mdl) { + if (logger.isLoggable(Level.FINE)) { + logger.fine(" found it: " + o); // NOI18N + } + wizardPage.userInputReceived((Component) o, e); + break; + } + } + } else if (e instanceof DocumentEvent) { + logger.fine("Looking for a JTextComponent for a DocumentEvent"); // NOI18N + Document document = ((DocumentEvent) e).getDocument(); + for (Iterator i = listenedTo.iterator(); i.hasNext();) { + Object o = i.next(); + if (o instanceof JTextComponent && ((JTextComponent) o).getDocument() == document) { + if (logger.isLoggable(Level.FINE)) { + logger.fine(" found it: " + o); // NOI18N + } + wizardPage.userInputReceived((Component) o, e); + break; + } + } + } else if (e instanceof ListSelectionEvent) { + logger.fine("Looking for a JList or JTable for a ListSelectionEvent"); // NOI18N + ListSelectionModel model = (ListSelectionModel) ((ListSelectionEvent) e).getSource(); + for (Iterator i = listenedTo.iterator(); i.hasNext();) { + Object o = i.next(); + if (o instanceof JList && ((JList) o).getSelectionModel() == model) { + if (logger.isLoggable(Level.FINE)) { + logger.fine(" found it: " + o); // NOI18N + } + wizardPage.userInputReceived((Component) o, e); + break; + } else if (o instanceof JTable && ((JTable) o).getSelectionModel() == model) { + if (logger.isLoggable(Level.FINE)) { + logger.fine(" found it: " + o); // NOI18N + } + wizardPage.userInputReceived((Component) o, e); + break; + } + } + } else { + wizardPage.userInputReceived(null, e); + } + } finally { + setIgnoreEvents(false); + } + } + } + + public void actionPerformed(ActionEvent e) { + fire(e); + } + + public void propertyChange(PropertyChangeEvent e) { + if (e.getSource() instanceof JComponent && "name".equals(e.getPropertyName())) { + // Note - most components do NOT fire a property change on + // setName(), but it is possible for this to be done intentionally + if (e.getOldValue() instanceof String) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("Name of component changed from " + e.getOldValue() + // NOI18N + " to " + e.getNewValue() + ". Removing any values for " + // NOI18N + e.getOldValue() + " from the wizard data map"); // NOI18N + } + wizardPage.removeFromMap(e.getOldValue()); + } + + if (logger.isLoggable(Level.FINE)) { + logger.fine("Possibly update map for renamed component " + // NOI18N + e.getSource()); + } + + } else if (e.getSource() instanceof JFormattedTextField && "value".equals(e.getPropertyName())) { + fire(e); + wizardPage.maybeUpdateMap((JComponent) e.getSource()); + } + } + + public void itemStateChanged(ItemEvent e) { + fire(e); + } + + public void componentAdded(ContainerEvent e) { +// if (extListener != null && extListener.accept(e.getChild())) { +// extListener.startListeningTo(e.getChild(), extNotifier); +// listenedTo.add (e.getChild()); +// } else if (accept(e.getChild())) { + if (accept (e.getChild())) { + attachTo(e.getChild()); + } + } + + public void componentRemoved(ContainerEvent e) { + if (extListener != null && extListener.accept (e.getChild())) { + extListener.stopListeningTo (e.getChild()); + listenedTo.remove (e.getChild()); + } else if (accept(e.getChild())) { + detachFrom(e.getChild()); + } + } + + public void insertUpdate(DocumentEvent e) { + fire(e); + } + + public void changedUpdate(DocumentEvent e) { + fire(e); + } + + public void removeUpdate(DocumentEvent e) { + fire(e); + } + + public void stateChanged(ChangeEvent e) { + fire(e); + } + + public void valueChanged(ListSelectionEvent e) { + fire(e); + } + + public void valueChanged(TreeSelectionEvent e) { + fire(e); + } + + public void tableChanged(TableModelEvent e) { + fire(e); + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/ResultProgressHandle.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/ResultProgressHandle.java new file mode 100644 index 000000000..b5afb105c --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/ResultProgressHandle.java @@ -0,0 +1,89 @@ +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.awt.Container; + +/** + * A controller for the progress bar shown in the user interface. Used in + * conjunction with DeferredWizardResult for cases where at + * the conclusion of the wizard, the work to create the final wizard result + * will take a while and needs to happen on a background thread. + * @author Tim Boudreau + */ +public interface ResultProgressHandle { + + /** + * Set the current position and total number of steps. Note it is + * inadvisable to be holding any locks when calling this method, as it + * may immediately update the GUI using + * EventQueue.invokeAndWait(). + * + * @param currentStep the current step in the progress of computing the + * result. + * @param totalSteps the total number of steps. Must be greater than + * or equal to currentStep. + */ + public abstract void setProgress (int currentStep, int totalSteps); + + /** + * Set the current position and total number of steps, and description + * of what the computation is doing. Note it is + * inadvisable to be holding any locks when calling this method, as it + * may immediately update the GUI using + * EventQueue.invokeAndWait(). + * @param description Text to describe what is being done, which can + * be displayed in the UI. + * @param currentStep the current step in the progress of computing the + * result. + * @param totalSteps the total number of steps. Must be greater than + * or equal to currentStep. + */ + public abstract void setProgress (String description, int currentStep, int totalSteps); + + /** + * Set the status as "busy" - a rotating icon will be displayed instead + * of a percent complete progress bar. + * + * Note it is inadvisable to be holding any locks when calling this method, as it + * may immediately update the GUI using + * EventQueue.invokeAndWait(). + * @param description Text to describe what is being done, which can + * be displayed in the UI. + */ + public abstract void setBusy (String description); + + /** + * Call this method when the computation is complete, and pass in the + * final result of the computation. The method doing the computation + * (DeferredWizardResult.start() or something it + * called) should exit immediately after calling this method. If the + * failed() method is called after this method has been + * called, a runtime exception may be thrown. + * @param result the Object which was computed, if any. + */ + public abstract void finished(Object result); + /** + * Call this method if computation fails. The message may be some text + * describing what went wrong, or null if no description. + * @param message The text to display to the user. The method + * doing the computation (DeferredWizardResult.start() or something it + * called). If the finished() method is called after this + * method has been called, a runtime exception may be thrown. + * should exit immediately after calling this method. + * It is A description of what went wrong, or null. + * @param canNavigateBack whether or not the Prev button should be + * enabled. + */ + public abstract void failed (String message, boolean canNavigateBack); + + /** + * Add the component to show for the progress display to the instructions panel. + */ + public abstract void addProgressComponents (Container panel); + + /** + * Returns true if the computation is still running, i.e., if neither finished or failed have been called. + * + * @return true if there is no result yet. + */ + public boolean isRunning(); +} \ No newline at end of file diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/SimpleWizard.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/SimpleWizard.java new file mode 100644 index 000000000..86982a26e --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/SimpleWizard.java @@ -0,0 +1,208 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + /* + * SimpleWizard.java + * + * Created on February 22, 2005, 2:33 PM + */ +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.swing.JComponent; + +/** + * A simple implementation of Wizard for use in wizards which have a + * straightforward set of steps with no branching. To use, implement the + * simplified interface SimpleWizard.Info and pass that to the constructor. + * + * @see SimpleWizardInfo + * @author Tim Boudreau + */ +final class SimpleWizard implements WizardImplementation { + + private final List listenerList + = Collections.synchronizedList(new LinkedList()); + private final Map ids2panels = new HashMap(); + + final SimpleWizardInfo info; + + private String currID = null; + private boolean subwizard; + + public SimpleWizard(WizardPanelProvider prov) { + this(new SimpleWizardInfo(prov), false); + } + + /** + * Creates a new instance of SimpleWizard + */ + public SimpleWizard(SimpleWizardInfo info) { + this.info = info; + info.setWizard(this); + } + + /** + * Creates a new instance of SimpleWizard + */ + public SimpleWizard(SimpleWizardInfo info, boolean subwizard) { + this.info = info; + this.subwizard = subwizard; + info.setWizard(this); + } + + public void addWizardObserver(WizardObserver observer) { + listenerList.add(observer); + } + + public void removeWizardObserver(WizardObserver observer) { + listenerList.remove(observer); + } + + public int getForwardNavigationMode() { + int result = info.getFwdNavMode(); + if (!subwizard && ((result & WizardController.MODE_CAN_CONTINUE) != 0) && isLastStep()) + result = WizardController.MODE_CAN_FINISH; + return result; + } + + boolean isLastStep() { + String[] steps = info.getSteps(); + return currID != null && steps.length > 0 && currID.equals(steps[steps.length - 1]); + } + + public String[] getAllSteps() { + String[] allSteps = info.getSteps(); + String[] result = new String[allSteps.length]; + //Defensive copy + System.arraycopy(allSteps, 0, result, 0, allSteps.length); + return result; + } + + public String getStepDescription(String id) { + int idx = Arrays.asList(info.getSteps()).indexOf(id); + if (idx == -1) + throw new IllegalArgumentException("Undefined id: " + id); + return info.getDescriptions()[idx]; + } + + public String getLongDescription(String id) { + return info.getLongDescription(id); + } + + public JComponent navigatingTo(String id, Map settings) { +// assert SwingUtilities.isEventDispatchThread(); + + // if info.getSteps() does not yet contain the ID, then create it + JComponent result = (JComponent) ids2panels.get(id); + currID = id; + if (result == null) { + result = info.createPanel(id, settings); + ids2panels.put(id, result); + } else { + info.update(); + info.recycleExistingPanel(id, settings, result); + } + fireSelectionChanged(); + return result; + } + + public String getCurrentStep() { + return currID; + } + + public String getNextStep() { + if (!info.isValid()) + return null; + if ((info.getFwdNavMode() & WizardController.MODE_CAN_CONTINUE) == 0) + return null; + + int idx = currentStepIndex(); + if (idx < info.getSteps().length - 1) + return info.getSteps()[idx + 1]; + else + return null; + } + + public String getPreviousStep() { + int idx = currentStepIndex(); + if (idx < info.getSteps().length && idx > 0) + return info.getSteps()[idx - 1]; + else + return null; + } + + int currentStepIndex() { + int idx = 0; + if (currID != null) + idx = Arrays.asList(info.getSteps()).indexOf(currID); + return idx; + } + + void fireNavigability() { + WizardObserver[] listeners = (WizardObserver[]) listenerList.toArray(new WizardObserver[0]); + + for (int i = listeners.length - 1; i >= 0; i--) { + WizardObserver l = (WizardObserver) listeners[i]; + l.navigabilityChanged(null); + } + } + + private void fireSelectionChanged() { + WizardObserver[] listeners = (WizardObserver[]) listenerList.toArray(new WizardObserver[0]); + + for (int i = listeners.length - 1; i >= 0; i--) { + WizardObserver l = (WizardObserver) listeners[i]; + l.selectionChanged(null); + } + } + + public Object finish(Map settings) throws WizardException { + return info.finish(settings); + } + + public boolean cancel(Map settings) { + return info.cancel(settings); + } + + public String getTitle() { + return info.getTitle(); + } + + public String getProblem() { + return info.getProblem(); + } + + public boolean isBusy() { + return info.isBusy(); + } + + public int hashCode() { + return info.hashCode() ^ 17; + } + + public boolean equals(Object o) { + if (o instanceof SimpleWizard) + return ((SimpleWizard) o).info.equals(info); + else + return false; + } + + public String toString() { + return "SimpleWizard for " + info; + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/SimpleWizardInfo.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/SimpleWizardInfo.java new file mode 100644 index 000000000..9654607ed --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/SimpleWizardInfo.java @@ -0,0 +1,331 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ + + /* + * SimpleWizardInfo.java + * + * Created on March 4, 2005, 9:46 PM + */ +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.awt.Color; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.lang.ref.WeakReference; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import javax.swing.BorderFactory; +import javax.swing.JComponent; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +/** + * Provides information about a simple wizard. Wraps a + * WizardPanelProvider and provides a connection to the instance of + * SimpleWizard created for it, acting as the WizardController for + * calls to WizardPanelProvider.createPanel(). + */ +final class SimpleWizardInfo implements WizardControllerImplementation { + + private WeakReference wizard = null; + private final String[] descriptions; + private final String[] steps; + final int[] navModeByPanel; + private String problem = null; + private final String title; + private final WizardPanelProvider provider; + private boolean busy = false; + + SimpleWizardInfo(WizardPanelProvider provider) { + this(provider.title, provider.steps, provider.descriptions, provider); + } + + /** + * Create an instance of Info, which will provide panels for a simple, + * non-branching wizard, passing a localized title, a list of steps + * and descriptions. + */ + protected SimpleWizardInfo(String title, String[] steps, String[] descriptions, WizardPanelProvider provider) { + if (steps == null) + throw new NullPointerException("Null steps"); + if (descriptions == null) + throw new NullPointerException("Null descriptions"); + this.steps = steps; + this.descriptions = descriptions; + if (new HashSet(Arrays.asList(steps)).size() < steps.length) + throw new IllegalArgumentException("Duplicate ID: " + Arrays.asList(steps)); + if (descriptions.length != steps.length) + if (steps.length != descriptions.length + 1 && !WizardImplementation.UNDETERMINED_STEP.equals(steps[steps.length - 1])) + throw new IllegalArgumentException("Steps and descriptions " + + "array lengths not equal: " + Arrays.asList(steps) + ":" + + Arrays.asList(descriptions)); + navModeByPanel = new int[steps.length]; + Arrays.fill(navModeByPanel, -1); + this.title = title; + this.provider = provider; + } + + final void setWizard(SimpleWizard wizard) { + this.wizard = new WeakReference(wizard); + } + + final SimpleWizard getWizard() { + return wizard != null ? (SimpleWizard) wizard.get() : null; + } + + final SimpleWizard createWizard() { + return new SimpleWizard(this); + } + + //pkg private for unit tests + final WizardController controller = new WizardController(this); + + /** + * Create a panel that represents a named step in the wizard. + * This method will be called exactly once in the life of + * a wizard. The panel should retain the passed settings Map, and + * add/remove values from it as the user enters information, calling + * setProblem() and setCanFinish() as + * appropriate in response to user input. + * + * @param id The name of the step, as supplied in the constructor + * @param settings A Map containing settings from earlier steps in + * the wizard + * + * @return A JComponent + */ + protected JComponent createPanel(String id, Map settings) { + try { + JComponent result = provider.createPanel(controller, id, settings); + if (result instanceof WizardPage) { + ((WizardPage) result).setController(controller); + ((WizardPage) result).setWizardDataMap(settings); + } + return result; + } catch (RuntimeException re) { + JTextArea jta = new JTextArea(); + jta.setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.RED)); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + PrintStream str = new PrintStream(buf); + re.printStackTrace(str); + jta.setText(new String(buf.toByteArray())); + setProblem(re.getLocalizedMessage()); + return new JScrollPane(jta); + } + } + + /** + * Instantiate whatever object (if any) the wizard creates from its + * gathered data. + */ + protected Object finish(Map settings) throws WizardException { + //XXX fixme +// assert canFinish(); + + // SKNUTSON: the "canFinish" behavior is not working + // instead, panels must implement the WizardPanel interface + // and have allowFinish return false +// if ( ! canFinish()) +// { +// throw new RuntimeException ("Can't finish right now"); +// } + return provider.finish(settings); + } + + public String getLongDescription(String id) { + return provider.getLongDescription(id); + } + + /** + * The method provides a chance to call setProblem() or setCanFinish() when + * the user re-navigates to a panel they've already seen - in the case + * that the user pressed the Previous button and then the Next button. + *

+ * The default implementation does nothing, which is sufficient for + * most implementations. If whether this panel is valid or not could + * have changed because of changed data from a previous panel, + * you may want to override this method to ensure validity and currNavMode + * are set correctly. + *

+ * This method will not be called when a panel is first instantiated + * - + * createPanel() is expected to set validity and currNavMode + * appropriately. + *

+ * The settings Map passed to this method will always be the same + * Settings map instance that was passed to createPanel() + * when the panel was created. + */ + protected void recycleExistingPanel(String id, Map settings, JComponent panel) { + provider.recycle(id, controller, settings, panel); + } + + private int index() { + SimpleWizard wizard = getWizard(); + if (wizard != null) + return wizard.currentStepIndex(); + else + return 0; + } + + public final void setBusy(boolean value) { + if (value != busy) { + busy = value; + fire(); + } + } + + /** + * Set whether or not the contents of this panel are valid. When + * user-entered information in a panel changes, call this method as + * appropriate. + */ + public final void setProblem(String value) { + this.problem = value; + int idx = index(); + provider.setKnownProblem(problem, idx); + fire(); + } + + private int currNavMode = WizardController.MODE_CAN_CONTINUE; + + /** + * Set whether or not the Finish button should be enabled. Neither + * the Finish nor Next buttons will be enabled if setProblem has + * been called with non-null. + *

+ * Legal values are: WizardController.MODE_CAN_CONTINUE, + * WizardController.MODE_CAN_FINISH or + * WizardController.MODE_CAN_CONTINUE_OR_FINISH. + *

+ * This method is used to set what means of forward navigation should + * be available if the current panel is in a valid state (problem is + * null). It is not a way to disable both the next button + * and the finish button, only a way to choose either or both. + * + * @param value The forward navigation mode + * + * @see setProblem + */ + public final void setForwardNavigationMode(int value) { + switch (value) { + case WizardController.MODE_CAN_CONTINUE: + case WizardController.MODE_CAN_FINISH: + case WizardController.MODE_CAN_CONTINUE_OR_FINISH: + break; + default: + throw new IllegalArgumentException(Integer.toString(value)); + } + if (currNavMode != value) { + currNavMode = value; + fire(); + } + navModeByPanel[index()] = value; + } + + public final int getFwdNavMode() { + return currNavMode; + } + + final String getTitle() { + return title; + } + + final void update() { + int idx = index(); + boolean change = navModeByPanel[idx] != -1 && currNavMode != navModeByPanel[idx]; + setProblem(provider.getKnownProblem(idx)); + currNavMode = navModeByPanel[idx] == -1 ? WizardController.MODE_CAN_CONTINUE : navModeByPanel[idx]; + if (change) + fire(); + } + + final void fire() { + WizardImplementation wiz = getWizard(); + if (wiz != null) + getWizard().fireNavigability(); + } + + final boolean isValid() { + return getProblem() == null; + } + + final boolean canFinish() { + return isValid() && (currNavMode != -1 && (currNavMode + & WizardController.MODE_CAN_FINISH) != 0); + } + + final boolean canContinue() { + return isValid() && (currNavMode == -1 || (currNavMode + & WizardController.MODE_CAN_CONTINUE) != 0); + } + + String[] getDescriptions() { + return descriptions; + } + + String[] getSteps() { + return steps; + } + + // lookup the step by name + boolean containsStep(String name) { + for (int i = 0; i < steps.length; i++) { + String step = steps[i]; + if (name.equals(step)) + return true; + } + return false; + } + + final String getProblem() { + return problem; + } + + boolean isBusy() { + return busy; + } + + public boolean equals(Object o) { + if (o != null && o.getClass() == getClass()) { + SimpleWizardInfo info = (SimpleWizardInfo) o; + + // assert info.descriptions != null : "Info.descriptions == null"; + // assert info.steps != null : "Info.steps == null"; + if (info.descriptions == null || info.steps == null) + throw new RuntimeException("Invalid info object"); + + return Arrays.equals(info.descriptions, descriptions) + && Arrays.equals(info.steps, steps) + && info.title == title; + } else + return false; + } + + public int hashCode() { + int result = 0; + for (int i = 0; i < steps.length; i++) + result += (steps[i].hashCode() * (i + 1)) ^ 31; + return result + (title == null ? 0 : title.hashCode()); + } + + boolean cancel(Map settings) { + return provider.cancel(settings); + } + + public String toString() { + return "SimpleWizardInfo@" + System.identityHashCode(this) + " for " + + provider; + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Summary.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Summary.java new file mode 100644 index 000000000..6e35464ed --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Summary.java @@ -0,0 +1,149 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +/* + * Summary.java + * + * Created on September 24, 2006, 4:05 AM + * + * To change this template, choose Tools | Template Manager + * and open the template in the editor. + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.awt.Component; +import java.awt.Font; +import javax.swing.JList; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.UIManager; + +/** + * Object which may be returned from WizardPage.WizardResultProducer.finish() + * or WizardPanelProvider.finish(), or passed to + * DeferredWizardResult.ResultProgressHandle.finish(). If an + * instance of Summary is used, then the UI should, rather + * than disappearing, show the component provided by the Summary + * object. Convenience constructors are provided for plain text and list style + * views. + * + * @author Tim Boudreau + */ +public class Summary { + private final Component comp; + private Object result; + + //constructors package private - only unit tests should be able to subclass + //Summary + + Summary(String text, Object result) { + //XXX this is creating components off the AWT thread - needs to change + //to use invokeAndWait where appropriate + if (text == null) { + throw new NullPointerException ("Text is null"); //NOI18N + } + if (text.trim().length() == 0) { + throw new IllegalArgumentException ("Text is empty or all " + //NOI18N + "whitespace"); //NOI18N + } + this.result = result; + JTextArea jta = new JTextArea(); + jta.setText (text); + jta.setWrapStyleWord(true); + jta.setLineWrap(true); + jta.getCaret().setBlinkRate(0); + jta.setEditable(false); + jta.getCaret().setVisible(true); + Font f = UIManager.getFont ("Label.font"); + if (f != null) { //may be on old GTK L&F, etc. + jta.setFont (f); + } + comp = new JScrollPane (jta); + } + + Summary(String[] items, Object result) { + if (items == null) { + throw new NullPointerException ("Items array null"); //NOI18N + } + if (items.length == 0) { + throw new IllegalArgumentException ("Items array empty"); //NOI18N + } + this.result = result; + JList list = new JList(items); + comp = new JScrollPane (list); + } + + Summary(Component comp, Object result) { + this.result = result; + this.comp = comp; + if (comp == null) { + throw new NullPointerException ("Null component"); //NOI18N + } + } + + /** + * Create a Summary object that will display the passed + * Strings in a JList or similar. + * @param items A non-null list of one or more Strings to be displayed + * @param result The result that should be returned when the Wizard is + * closed + * @return the requested Summary object + */ + public static Summary create (String[] items, Object result) { + return new Summary (items, result); + } + + /** + * Create a Summary object that will display the passed component. + * @param comp A custom component to show on the summary page after the + * Wizard has been completed + * @param result The result that should be returned when the Wizard is + * closed + * @return the requested Summary object + */ + public static Summary create (Component comp, Object result) { + return new Summary (comp, result); + } + + /** + * Create a Summary object which will display the + * passed String in a text component of some sort. + * @param text The text to display - must be non-null, greater than zero + * length and not completely whitespace + * @param result The result that should be returned when the Wizard is + * closed + * @return the requested Summary object + */ + public static Summary create (String text, Object result) { + return new Summary (text, result); + } + + /** + * Get the component that will display the summary information. + * @return an appropriate component, the type of which may differ depending + * on the factory method used to create this component + */ + public Component getSummaryComponent() { + return comp; + } + + /** + * Get the object that represents the actual result of whatever the Wizard + * that created this Summary object computes. Note this method may not + * return another instance of Summary or an instance of + * DeferredWizardResult. + * @return the object passed to the factory method that created this + * Summary object, or null. + */ + public Object getResult() { + return result; + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Util.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Util.java new file mode 100644 index 000000000..58229cd58 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Util.java @@ -0,0 +1,174 @@ +/* + * To change this template, choose Tools | Templates + * and open the template in the editor. + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.HashSet; +import java.util.Set; + +/** + * + * @author Tim Boudreau + */ +final class Util { + private Util(){} + + /** + * Get an array of step ids from an array of WizardPages + */ + static String[] getSteps(WizardPage[] pages) { + String[] result = new String[pages.length]; + + Set uniqueNames = new HashSet(pages.length); + for (int i = 0; i < pages.length; i++) { + result[i] = pages[i].id(); + if (result[i] == null || uniqueNames.contains(result[i])) { + result[i] = uniquify (getIDFromStaticMethod(pages[i].getClass()), + uniqueNames); + pages[i].id = result[i]; + } + uniqueNames.add (result[i]); + } + return result; + } + + static String uniquify (String s, Set /* */ used) { + String test = s; + if (test != null) { + int ix = 0; + while (used.contains(test)) { + test = s + "_" + ix++; + } + } + return test; + } + + /** + * Get an array of descriptions from an array of WizardPages + */ + static String[] getDescriptions(WizardPage[] pages) { + String[] result = new String[pages.length]; + + for (int i = 0; i < pages.length; i++) { + result[i] = pages[i].description(); + if (result[i] == null) { + result[i] = getDescriptionFromStaticMethod (pages[i].getClass()); + } + } + + return result; + } + + static String getIDFromStaticMethod (Class clazz) { + // System.err.println("GetID by method for " + clazz); + String result = null; + try { + Method m = clazz.getDeclaredMethod("getStep", new Class[] {}); + // assert m.getReturnType() == String.class; + result = (String) m.invoke(clazz, (Object[]) null); + if (result == null) { + throw new NullPointerException ("getStep may not return null"); + } + } catch (Exception ex) { + //do nothing + } + return result == null ? clazz.getName() : result; + } + + /** + * Get an array of steps by looking for a static method getID() on each + * class object passed + */ + static String[] getSteps(Class[] pages) { + if (pages == null) { + throw new NullPointerException("Null array of classes"); //NOI18N + } + + String[] result = new String[pages.length]; + + Set used = new HashSet (pages.length); + for (int i = 0; i < pages.length; i++) { + if (pages[i] == null) { + throw new NullPointerException("Null at " + i + " in array " + //NOI18N + "of panel classes"); //NOI18N + } + + if (!WizardPage.class.isAssignableFrom(pages[i])) { + throw new IllegalArgumentException(pages[i] + + " is not a subclass of WizardPage"); //NOI18N + } + result[i] = uniquify (getIDFromStaticMethod (pages[i]), used); + if (result[i] == null) { + result[i] = pages[i].getName(); + } + } + // System.err.println("Returning " + Arrays.asList(result)); + return result; + } + +// /** Determine if a default constructor is present for a class */ +// private static boolean hasDefaultConstructor (Class clazz) { +// try { +// Constructor c = clazz.getConstructor(new Class[0]); +// return c != null; +// } catch (Exception e) { +// return false; +// } +// } + + /** + * Get an array of descriptions by looking for the static method + * getDescription() on each passed class object + */ + static String[] getDescriptions(Class[] pages) { + String[] result = new String[pages.length]; + + for (int i = 0; i < pages.length; i++) { + result[i] = getDescriptionFromStaticMethod(pages[i]); + } + + return result; + } + + static String getDescriptionFromStaticMethod(Class clazz) { + String result = null; + Method m; + try { + m = clazz.getDeclaredMethod("getDescription", (Class[]) null); //NOI18N + } catch (Exception e) { + throw new IllegalArgumentException("Could not find or access " + //NOI18N + "public static String " + clazz.getName() + //NOI18N + ".getDescription() - make sure it exists"); //NOI18N + } + + if (m.getReturnType() != String.class) { + throw new IllegalArgumentException("getStep has wrong " //NOI18N + + " return type: " + m.getReturnType() + " on " + //NOI18N + clazz); + } + + if (!Modifier.isStatic(m.getModifiers())) { + throw new IllegalArgumentException("getStep is not " + //NOI18N + "static on " + clazz); //NOI18N + } + + try { + m.setAccessible(true); + result= (String) m.invoke(null, (Object[]) null); + } catch (InvocationTargetException ite) { + throw new IllegalArgumentException("Could not invoke " + //NOI18N + "public static String " + clazz.getName() + //NOI18N + ".getDescription() - make sure it exists."); //NOI18N + } catch (IllegalAccessException iae) { + throw new IllegalArgumentException("Could not invoke " + //NOI18N + "public static String " + clazz.getName() + //NOI18N + ".getDescription() - make sure it exists."); //NOI18N + } + return result; + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Wizard.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Wizard.java new file mode 100644 index 000000000..bf735b8fb --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/Wizard.java @@ -0,0 +1,364 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.awt.Rectangle; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import javax.swing.Action; +import javax.swing.JComponent; +import org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer; + +/** + * Encapsulates the logic and state of a Wizard. A Wizard gathers information + * into a Map, and then performs some action with that information when the + * user clicks Finish. To display a wizard, pass it to one of the methods + * on WizardDisplayer.getDefault(). + *

+ * A Wizard is a series of one or more steps represented + * by panels in the user interface. Each step is identified by a unique String ID. + * Panels are created, usually on-demand, as the user navigates through + * the UI of the wizard. Panels typically listen on components they contain + * and put values into the Map where the wizard gathers data. Note that if the + * user navigates backward, data entered on pages after the current one + * disappears from the Map. + *

+ * To create a Wizard, you do not implement or instantiate this class directly, + * but rather, use one of the convenience classes in this package. There are + * three: + *

    + *
  • WizardPage - use or subclass WizardPage, and pass an array + * of instances, or an array of the Class objects of your subclasses + * to WizardPage.createWizard(). This class offers the added + * convenience that standard Swing components will be listened to automatically, + * and if their Name property is set, the value from the component will be + * automatically put into the settings map. + *
  • + * + *
  • WizardPanelProvider - subclass this to create a Wizard + * with a fixed set of steps. You provide a set of unique ID strings to the + * constructor, for all of the steps in the wizard, and override + * createPanel() to create the GUI component that should be displayed for + * each step - it will be called on demand as the user moves through the + * wizard
  • + * + *
  • WizardBranchController - this is for creating complex + * wizards with decision points after which the future steps change, depending + * on what the user chooses. Create a simple wizard using WizardPage or + * WizardPanelProvider to represent the initial steps. + * Then override getWizardForStep() or + * getPanelProviderForStep() to return a different Wizard to + * represent the remaining steps at any point where the set of future steps + * changes. You can have as many branch points as you want, simply by + * using WizardBranchController to create the wizards for different decision + * points. + *

    + * In other words, a wizard with a different set of panels (or number of steps) + * depending on the user's decision is really three wizards composed into one - + * one wizard that provides the initial set of steps, and then two others, one + * or the other of which will actually provide the steps/panels after the + * decision point (the Wizards are created on demand, for efficiency, so if + * the user never changes his or her mind at the decision point, only two + * of the three Wizards are ever actually created). + *

+ * + * @see org.jackhuang.hellominecraft.utils.views.wizard.api.WizardDisplayer + * @see WizardPage + * @see WizardPanelProvider + * @see WizardBranchController + * + * @author Timothy Boudreau + */ +public final class Wizard { + /** + * Constant that can be returned by getForwardNavigationMode() + * to indicate that the Next button can be enabled (or the Finish button + * if the current panel is the last one in the wizard). + */ + public static final int MODE_CAN_CONTINUE = + WizardController.MODE_CAN_CONTINUE; + + /** + * Constant that can be returned by getForwardNavigationMode to indicate + * that the Finish button can be enabled if the problem string is null. + */ + public static final int MODE_CAN_FINISH = + WizardController.MODE_CAN_FINISH; + /** + * Constant that can be returned by getForwardNavigationMode to indicate + * that both the Finish and Next buttons can be enabled if the problem + * string is null. This value is a bitmask - i.e. + * MODE_CAN_CONTINUE_OR_FINISH == MODE_CAN_CONTINUE | + * MODE_CAN_FINISH + */ + public static final int MODE_CAN_CONTINUE_OR_FINISH = + WizardController.MODE_CAN_CONTINUE_OR_FINISH; + + /** + * Special panel ID key indicating a branch point in the wizard, + * after which the next step(s) are unknown. + */ + public static final String UNDETERMINED_STEP = "_#UndeterminedStep"; + + + final WizardImplementation impl; //package private for unit tests + + /** Creates a new instance of Wizard */ + Wizard(WizardImplementation impl) { + this.impl = impl; + if (impl == null) { + throw new NullPointerException(); + } + } + + /** + * Notify the wizard that the user is navigating to a different panel, + * as identified by the passed id. + * @param id The id of the panel being navigated to + * @param wizardData The data gathered thus far as the user has progressed + * through the wizard. The contents of this map should not contain any + * key/values that were assigned on future panels, if the user is + * navigating backward. + * @return The component that should be shown for step id + * of the Wizard + */ + public JComponent navigatingTo(String id, Map wizardData) { + return impl.navigatingTo(id, wizardData); + } + + /** + * Get the current step the wizard is on, as determined by the most recent + * call to navigatingTo(). + */ + public String getCurrentStep() { + return impl.getCurrentStep(); + } + + /** + * Get the id of the step that comes after current step returned by + * getCurrentStep(). + * @return Null if this is the last step of the wizard; + * UNDETERMINED_STEP if this is a branch point and the + * user yet needs to do some interaction with the UI of the current + * panel to trigger computation of the id of the next step; otherwise, + * the unique id of the next step. + */ + public String getNextStep() { + return impl.getNextStep(); + } + + /** + * Get the id of the preceding step to the current one as returned by + * getCurrentStep(), or null if the current step is the + * first page of the wizard. + * @return the id of the previous step or null + */ + public String getPreviousStep() { + return impl.getPreviousStep(); + } + + /** + * Get the problem string that should be displayed to the user. + * @return A string describing what the user needs to do to enable + * the Next or Finish buttons, or null if the buttons may be enabled + */ + public String getProblem() { + return impl.getProblem(); + } + + /** + * Get the string IDs of all known steps in this wizard, terminating + * with UNDETERMINED_STEP if subsequent steps of the + * wizard depend on the user's interaction beyond that point. + * @return an array of strings which may individually be passed to + * navigatingTo to change the current step of the wizard + */ + public String[] getAllSteps() { + return impl.getAllSteps(); + } + + /** + * Get a long description for this panel. The long description should be + * used in preference to the short description in the top of a wizard + * panel in the UI, if it returns non-null. + * @param stepId The ID of the step for which a description is requested + * @return A more detailed localized description or null + */ + public String getLongDescription(String stepId) { + return impl.getLongDescription (stepId); + } + + /** + * Get a localized String description of the step for the passed id, + * which may be displayed in the UI of the wizard. + * @param id A step id among those returned by getAllSteps() + */ + public String getStepDescription(String id) { + return impl.getStepDescription(id); + } + + /** + * Called when the user has clicked the finish button. This method + * computes whatever the result of the wizard is. + * @param settings The complete set of key-value pairs gathered by the + * various panels as the user proceeded through the wizard + * @return An implementation-dependent object that is the outcome of + * the wizard. May be null. Special return values are instances of + * DeferredWizardResult and Summary which will affect the behavior of + * the UI. + */ + public Object finish(Map settings) throws WizardException { + return impl.finish(settings); + } + + /** + * Called when the user has clicked the Cancel button in the wizard UI + * or otherwise closed the UI component without completing the wizard. + * @param settings The (possibly incomplete) set of key-value pairs gathered by the + * various panels as the user proceeded through the wizard + * @return true if the UI may indeed be closed, false if closing should + * not be permitted + */ + public boolean cancel (Map settings) { + return impl.cancel(settings); + } + + /** + * Get the title of the Wizard that should be displayed in its dialog + * titlebar (if any). + * @return A localized string + */ + public String getTitle() { + return impl.getTitle(); + } + + /** + * Determine if the wizard is busy doing work in a background thread and + * all navigation controls should be disabled. + * @return whether or not the wizard is busy + */ + public boolean isBusy() { + return impl.isBusy(); + } + + /** + * Get the navigation mode, which determines the enablement state of + * the Next and Finish buttons. + * @return one of the constants MODE_CAN_CONTINUE, + * MODE_CAN_FINISH, or MODE_CAN_CONTINUE_OR_FINISH. + */ + public int getForwardNavigationMode() { + return impl.getForwardNavigationMode(); + } + + private volatile boolean listeningToImpl = false; + private final List listeners = Collections.synchronizedList ( + new LinkedList()); + + private WizardObserver l = null; + /** + * Add a WizardObserver that will be notified of navigability and step + * changes. + * @param observer A WizardObserver + */ + public void addWizardObserver(WizardObserver observer) { + listeners.add(observer); + if (!listeningToImpl) { + l = new ImplL(); + impl.addWizardObserver(l); + listeningToImpl = true; + } + } + + /** + * Remove a WizardObserver. + * @param observer A WizardObserver + */ + public void removeWizardObserver(WizardObserver observer) { + listeners.remove(observer); + if (listeningToImpl && listeners.size() == 0) { + impl.removeWizardObserver(l); + l = null; + listeningToImpl = false; + } + } + + private class ImplL implements WizardObserver { + public void stepsChanged(Wizard wizard) { + WizardObserver[] l = (WizardObserver[]) listeners.toArray( + new WizardObserver[listeners.size()]); + for (int i = 0; i < l.length; i++) { + l[i].stepsChanged(Wizard.this); + } + } + + public void navigabilityChanged(Wizard wizard) { + WizardObserver[] l = (WizardObserver[]) listeners.toArray( + new WizardObserver[listeners.size()]); + for (int i = 0; i < l.length; i++) { + l[i].navigabilityChanged(Wizard.this); + } + } + + public void selectionChanged(Wizard wizard) { + WizardObserver[] l = (WizardObserver[]) listeners.toArray( + new WizardObserver[listeners.size()]); + for (int i = 0; i < l.length; i++) { + l[i].selectionChanged(Wizard.this); + } + } + } + + public int hashCode() { + return impl.hashCode() * 17; + } + + public boolean equals (Object o) { + if (o == this) { + return true; + } else if (o instanceof Wizard) { + return impl.equals (((Wizard)o).impl); + } else { + return false; + } + } + + /** + * Delegates to WizardDisplayer.showWizard() + */ + public void show () { + WizardDisplayer.showWizard(this); + } + + /** + * Delegates to WizardDisplayer.showWizard() + */ + public Object show (Wizard wizard, Action help) { + return WizardDisplayer.showWizard (wizard, help); + } + + /** + * Delegates to WizardDisplayer.showWizard() + */ + public Object show (Wizard wizard, Rectangle r) { + return WizardDisplayer.showWizard (wizard, r); + } + + /** + * Delegates to WizardDisplayer.showWizard() + */ + public Object show (Wizard wizard, Rectangle r, Action help) { + return WizardDisplayer.showWizard (wizard, r, help, null); + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardBranchController.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardBranchController.java new file mode 100644 index 000000000..f24a0c603 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardBranchController.java @@ -0,0 +1,164 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +/* + * WizardBranchController.java + * + * Created on March 5, 2005, 6:33 PM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; +import java.util.Map; + +/** + * Extend this class to create wizards which have branch points in them - + * either override getWizardForStep to return one or another a wizard which + * represents the subsequent steps after a decision point, or override + * getPanelProviderForStep to provide instances of WizardPanelProvider + * if there are no subsequent branch points and the continuation is a + * simple wizard. + *

+ * The basic idea is to supply a base wizard for the initial steps, stopping + * at the branch point. The panel for the branch point should put enough + * information into the settings map that the WizardBranchController can + * decide what to return as the remaining steps of the wizard. + *

+ * The result is a Wizard which embeds sub-wizards; when the + * PanelProvider passed to the constructor runs out of steps, + * the master Wizard will try to find a sub-Wizard + * by calling getWizardForStep. If non-null, the user seamlessly + * continues in the returned wizard. To create Wizards with + * multiple branches, simply override getWizardForStep to create + * another WizardBranchController and return the result of its + * createWizard method. + *

+ * Note that it is important to cache the instances of WizardPanelProvider + * or Wizard which are returned here - this class's methods may + * be called frequently to determine if the sequence of steps (the next wizard) + * have changed. + * + * @author Tim Boudreau + */ +public abstract class WizardBranchController { + private final SimpleWizardInfo base; + + /** + * Create a new WizardBranchController. The base argument + * provides the initial step(s) of the wizard up; when the user comes to + * the last step of the base wizard, this WizardBranchController will be + * asked for a wizard to provide subsequent panes. So the base wizard + * should put some token into the settings map based on what the user + * selects on its final pane, which the WizardBranchController can use + * to decide what the next steps should be. + */ + protected WizardBranchController (WizardPanelProvider base) { + this (new SimpleWizardInfo (base)); + } + + /** + * Create a new WizardBranchController using the passed WizardPage + * instances as the initial pages of the wizard. + * @param pages An array of WizardPage instances + */ + protected WizardBranchController (WizardPage[] pages) { + this (WizardPage.createWizardPanelProvider(pages)); + } + + /** + * Create a new WizardBranchController using the passed WizardPage + * as the initial page of the wizard. The initial page should + * determine the subsequent steps of the wizard. + * @param onlyPage An instance of WizardPage + */ + protected WizardBranchController (WizardPage onlyPage) { + this (WizardPage.createWizardPanelProvider(onlyPage)); + } + /** + * Create a new WizardBranchController, using the passed SimpleWizardInfo + * for the initial panes of the wizard. + */ + WizardBranchController (SimpleWizardInfo base) { + if (base == null) throw new NullPointerException ("No base"); + this.base = base; + } + + /** + * Get the wizard which represents the subsequent panes after this step. + * The UI for the current step should have put sufficient data into the + * settings map to decide what to return; return null if not. + *

+ * The default implementation delegates to getPanelProviderForStep() + * and returns a Wizard representing the result of that + * call. + *

+ * Note: This method can be called very frequently, to determine + * if the sequence of steps has changed - so it needs to run fast. + * Returning the same instance every time the same arguments are passed + * is highly recommended. It will typically be called whenever a change + * is fired by the base wizard (i.e. every call setProblem() + * should generate a check to see if the navigation has changed). + *

+ * Note that the wizard for the subsequent steps will be instantiated + * as soon as it is known what the user's choice is, so the list of + * pending steps can be updated (this does not mean that all subsequent + * panel UI components of the wizard will be instantiated, just the + * Wizard object itself, which will create panels on demand if they + * have not already been created). + * + * @param step The current step the user is on in the wizard + * @param settings The settings map, which previous panes of the wizard + * have been writing information into + */ + protected Wizard getWizardForStep(String step, Map settings) { + WizardPanelProvider provider = getPanelProviderForStep(step, settings); + return provider == null ? null : provider.createWizard(); + } + + /** + * Override this method to return a WizardPanelProvider representing the + * steps from here to the final step of the wizard, varying the returned + * object based on the contents of the map and the step in question. + * The default implementation of this method throws an Error - + * either override this method, or override getWizardForStep() + * (in which case this method will not be called). + *

+ * Note: This method can be called very frequently, to determine + * if the sequence of steps has changed - so it needs to run fast. + * Returning the same instance every time called with equivalent arguments + * is highly recommended. + * + * @param step The string ID of the current step + * @param settings The settings map, which previous panes of the wizard + * will have written content into + */ + protected WizardPanelProvider getPanelProviderForStep(String step, Map settings) { + throw new Error ("Override either createInfoForStep or " + + "createWizardForStep"); + } + + SimpleWizardInfo getBase() { + return base; + } + + private WizardImplementation wizard = null; + private Wizard real = null; + /** + * Create a Wizard to represent this branch controller. The resulting + * Wizard instance is cached; subsequent calls to this method will return + * the same instance. + */ + public final Wizard createWizard() { + if (wizard == null) { + wizard = new BranchingWizard (this); + real = new Wizard (wizard); + } + return real; + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardController.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardController.java new file mode 100644 index 000000000..7d5f06a35 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardController.java @@ -0,0 +1,120 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +/* + * WizardController.java + * + * Created on March 5, 2005, 7:24 PM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +/** + * Controller which can be used to modify the UI state of a wizard. Passed + * as an argument to methods of WizardPanelProvider. Use this + * interface + * to determine whether the Next/Finish buttons should be enabled, and if some + * problem explanation text should be displayed. + *

+ * If you are using {@link WizardPage WizardPage}, methods equivalent to this + * interface are available directly on instances of WizardPage. + * + * @see WizardPanelProvider + * @author Tim Boudreau + */ +public final class WizardController { + /** + * Constant that can be passed to setForwardNavigationMode to indicate + * that the Next button can be enabled if the problem string is null. + * Value is identical to the similarly named constant on Wizard. + */ + public static final int MODE_CAN_CONTINUE = 1; + /** + * Constant that can be passed to setForwardNavigationMode to indicate + * that the Finish button can be enabled if the problem string is null. + * Value is identical to the similarly named constant on Wizard. + */ + public static final int MODE_CAN_FINISH = 2; + /** + * Constant that can be passed to setForwardNavigationMode to indicate + * that both the Finish and Next buttons can be enabled if the problem + * string is null. This value is a bitmask - i.e. + * MODE_CAN_CONTINUE_OR_FINISH == MODE_CAN_CONTINUE | + * MODE_CAN_FINISH. + * Value is identical to the similarly named constant on + * Wizard. + */ + public static final int MODE_CAN_CONTINUE_OR_FINISH = + MODE_CAN_CONTINUE | MODE_CAN_FINISH; + + private final WizardControllerImplementation impl; + + WizardController (WizardControllerImplementation impl) { + this.impl = impl; + } + + /** + * Indicate that there is a problem with what the user has (or has not) + * input, such that the Next/Finish buttons should be disabled until the + * user has made some change. + *

+ * If you want to disable the Next/Finish buttons, do that by calling + * this method with a short description of what is wrong. + *

+ * Pass null to indicate there is no problem; non-null indicates there is + * a problem - the passed string should be a localized, human-readable + * description that assists the user in correcting the situation. It will + * be displayed in the UI. + */ + public void setProblem (String value) { + impl.setProblem (value); + } + + /** + * Set the forward navigation mode. This method determines whether + * the Next button, the Finish button or both should be enabled if the + * problem string is set to null. + *

+ * On panels where, based on the UI state, the only reasonable next + * step is to finish the wizard (even though there may be more panels + * if the UI is in a different state), set the navigation mode to + * MODE_CAN_FINISH, and the Finish button will be enabled, and the + * Next button not. + *

+ * On panels where, based on the UI state, the user could either continue + * or complete the wizard at that point, set the navigation mode to + * MODE_CAN_CONTINUE_OR_FINISH. + *

+ * If the finish button should not be enabled, set the navigation mode + * to MODE_CAN_CONTINUE. This is the default on any panel if no + * explicit call to setForwardNavigationMode() has been made. + * + * @param navigationMode Legal values are MODE_CAN_CONTINUE, + * MODE_CAN_FINISH or MODE_CAN_CONTINUE_OR_FINISH + */ + public void setForwardNavigationMode (int navigationMode) { + impl.setForwardNavigationMode(navigationMode); + } + + /** + * Indicate that some sort of background process is happening (presumably + * a progress bar is being shown to the user) which cannot be interrupted. + * Calling this menu disables all navigation and the ability to close + * the wizard dialog. Use this option with caution and liberal use of + * finally to reenable navigation. + */ + public void setBusy (boolean busy) { + impl.setBusy (busy); + } + + WizardControllerImplementation getImpl() { + return impl; + } +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardControllerImplementation.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardControllerImplementation.java new file mode 100644 index 000000000..a873a0f5a --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardControllerImplementation.java @@ -0,0 +1,70 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +/** + * Internal, non-public SPI for wizard controller; allows the actual WizardController + * class to be final, so it does not imply that the API user should implement + * it, and methods can be added safely to it if desired. + * + * @see WizardController + * @author Tim Boudreau + */ +interface WizardControllerImplementation { + /** + * Indicate that there is a problem with what the user has (or has not) + * input, such that the Next/Finish buttons should be disabled until the + * user has made some change. + *

+ * If you want to disable the Next/Finish buttons, do that by calling + * this method with a short description of what is wrong. + *

+ * Pass null to indicate there is no problem; non-null indicates there is + * a problem - the passed string should be a localized, human-readable + * description that assists the user in correcting the situation. It will + * be displayed in the UI. + */ + void setProblem (String value); + + /** + * Set the forward navigation mode. This method determines whether + * the Next button, the Finish button or both should be enabled if the + * problem string is set to null. + *

+ * On panels where, based on the UI state, the only reasonable next + * step is to finish the wizard (even though there may be more panels + * if the UI is in a different state), set the navigation mode to + * MODE_CAN_FINISH, and the Finish button will be enabled, and the + * Next button not. + *

+ * On panels where, based on the UI state, the user could either continue + * or complete the wizard at that point, set the navigation mode to + * MODE_CAN_CONTINUE_OR_FINISH. + *

+ * If the finish button should not be enabled, set the navigation mode + * to MODE_CAN_CONTINUE. This is the default on any panel if no + * explicit call to setForwardNavigationMode() has been made. + * + * @param navigationMode Legal values are MODE_CAN_CONTINUE, + * MODE_CAN_FINISH or MODE_CAN_CONTINUE_OR_FINISH + */ + void setForwardNavigationMode (int navigationMode); + + /** + * Indicate that some sort of background process is happening (presumably + * a progress bar is being shown to the user) which cannot be interrupted. + * Calling this menu disables all navigation and the ability to close + * the wizard dialog. Use this option with caution and liberal use of + * finally to reenable navigation. + */ + void setBusy (boolean busy); +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardException.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardException.java new file mode 100644 index 000000000..e66698d0f --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardException.java @@ -0,0 +1,52 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +/* + * WizardException.java + * + * Created on February 22, 2005, 3:56 PM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +/** + * Some arguments a user enters in a wizard may be too expensive to validate + * as the user is going through the wizard. Therefore, Wizard.finish() throws + * WizardException. + *

+ * Exceptions of this type always have a localized message, and optionally + * provide a step in the wizard that to return to, so that the user can + * enter corrected information. + * + * @author Tim Boudreau + */ +public final class WizardException extends Exception { + private final String localizedMessage; + private final String step; + /** Creates a new instance of WizardException */ + public WizardException(String localizedMessage, String stepToReturnTo) { + super ("wizardException"); + this.localizedMessage = localizedMessage; + this.step = stepToReturnTo; + } + + public WizardException (String localizedMessage) { + this (localizedMessage, Wizard.UNDETERMINED_STEP); + } + + public String getLocalizedMessage() { + return localizedMessage; + } + + public String getStepToReturnTo() { + return step; + } + +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardImplementation.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardImplementation.java new file mode 100644 index 000000000..d648e19c9 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardImplementation.java @@ -0,0 +1,246 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +/* + * Wizard.java + * + * Created on February 22, 2005, 2:18 PM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.util.Map; +import javax.swing.JComponent; + +/** + * Non-public mirror interface to the final Wizard class. A Wizard delegates to + * its WizardImplementation for all of its functions. This interface is + * implemented by several internal classes. + * + * @author Tim Boudreau + * @see Wizard + * @see WizardPage + * @see WizardPanelProvider + * @see WizardBranchController + * @see org.netbeans.api.wizard.WizardDisplayer + */ +interface WizardImplementation { + /** + * Constant that can be returned by getForwardNavigationMode to indicate + * that the Next button can be enabled if the problem string is null. + */ + public static final int MODE_CAN_CONTINUE = + WizardController.MODE_CAN_CONTINUE; + + /** + * Constant that can be returned by getForwardNavigationMode to indicate + * that the Finish button can be enabled if the problem string is null. + */ + public static final int MODE_CAN_FINISH = + WizardController.MODE_CAN_FINISH; + /** + * Constant that can be returned by getForwardNavigationMode to indicate + * that both the Finish and Next buttons can be enabled if the problem + * string is null. This value is a bitmask - i.e. + * MODE_CAN_CONTINUE_OR_FINISH == MODE_CAN_CONTINUE | + * MODE_CAN_FINISH + */ + public static final int MODE_CAN_CONTINUE_OR_FINISH = + WizardController.MODE_CAN_CONTINUE_OR_FINISH; + + /** + * Special panel ID key indicating a branch point in the wizard, + * after which the next step(s) are unknown. + */ + public static final String UNDETERMINED_STEP = "_#UndeterminedStep"; + + /** + * Set which step of the wizard is currently being displayed and get + * the component for that step. This method is passed a Map into which + * panels may put key/value pairs that represent user input. This Map + * is what the finish() method will use to decide what to + * do. + *

+ * The ID passed + * becomes the currently active step of the wizard as of this call. + *

+ * If the user has already been to this step, and some key/value pairs + * were written to the wizard data map, and the user then + * then pressed the Back button, and later pressed Next again, the + * wizard data map may already contain key/value pairs for this + * step. Panels whose components + * are affected by data entered in the map in preceding steps should + * update their UI based on the map's current contents, at the time a + * step is re-displayed, to ensure they are in + * sync with any changes the user may have made on preceding panels + * since the last time this panel was shown. + *

+ * If this method is called as a result of + * the user pressing the Back button, the wizard data map will not + * contain any key/value pairs added by the subsequent panels. It + * will only key/value pairs from + * panels that precede this one and any written the last time this + * panel was visited. The wizard data map shall never contain + * keys and values from future steps in the wizard. + *

+ * Implementations are expected to return the same component if + * navigatingTo() is called repeatedly with the same ID. + * Components should be constructed once, then reused for the lifetime + * of the wizard. + *

+ * Note: The consequences of a later panel writing or deleting + * a key/value pair that was put into the wizard data map by + * an earlier panel are + * undefined. Each step should use only its own unique keys, not + * modify those from earlier steps. + *
+ * + * + * @param id The ID of the to-be-current panel + * @param wizardData The map into which panels should write key/value pairs + * in response to user input - the place where user data is aggregated + * @return The UI component for this step, which should be displayed in + * the wizard + */ + public JComponent navigatingTo(String id, Map wizardData); + + /** + * Get the String ID of the current panel. + * If there is no current panel, return null. + * + * @return The unique ID of the step currently + * presented in the UI, as determined by the last call to + * navigateTo + */ + public String getCurrentStep(); + + /** + * Get the String ID of the next panel. If the Next button should be + * disabled, or this is the final step of the wizard, return null. + * + * @return The unique ID of the step that follows the one currently + * presented in the UI, as determined by the last call to + * navigateTo + */ + public String getNextStep(); + + /** + * Get the String ID of the previous panel. If the Prev button should + * be disabled, return null. + * @return the String ID of the step that precedes the one currently + * presented in this Wizards UI, or null if it is either + * the first step or the preceding step is unknown, as determined by the last call to + * navigateTo + */ + public String getPreviousStep(); + + /** + * Get a human readable description of the reason the Next/Finish button + * is not enabled (i.e. "#\foo is not a legal filename"). + * @return A localized string that describes why the Next/Finish button + * is not enabled, or null if one or the other or both should be enabled + */ + public String getProblem(); + + /** + * Get String IDs for the entire list of known steps in the + * wizard (regardless of whether + * Finish/Next can be enabled or not). If there is a branch point in + * the wizard and it cannot be determined what step will be next beyond + * that point, make the final entry in the returned array the constant + * UNDETERMINED_STEP, and fire stepsChange() to any listeners + * once the later steps become known. + *

+ * The return value of this method must be an array of Strings at least + * one String in length. If length == 1, the single step ID may not be + * UNDETERMINED_STEP; UNDETERMINED_STEP may only be the last ID, and only + * may be used if there is more than one step. + * + * @return An array of strings that constitute unique IDs of each step + * in the wizard. The returned array may not contain duplicate entries. + */ + public String[] getAllSteps(); + + /** + * Get a human-readable description for a given panel, as identified by + * the passed ID. + */ + public String getStepDescription(String id); + + public String getLongDescription(String id); + + /** + * Add a listener for changes in the count or order of steps in this + * wizard and for changes in Next/Previous/Finish button enablement. + * @param listener A listener to add + */ + public void addWizardObserver(WizardObserver listener); + + /** + * Remove a listener for changes in the count or order of steps in this + * wizard and for changes in Next/Previous/Finish button enablement. + * @param listener A listener to remove + */ + public void removeWizardObserver(WizardObserver listener); + + /** + * Finish the wizard, (optionally) instantiating some Object and returning + * it. For cases where the map may contain wizardData too expensive to + * validate on the fly, + * this method may throw a WizardException with a localized message + * indicating the problem; that exception can indicate a step in the + * wizard to return to to allow the user to correct the information. + *

+ * No methods on a Wizard instance should be called after + * that Wizard's finish() method has been + * called - the results are undefined. + * + * @param settings A map containing all of the wizardData the user has + * entered as they traversed this wizard - presumably enough to do + * whatever this method needs to do (if not, that's a bug in the + * implementation of Wizard). + */ + public Object finish(Map settings) throws WizardException; + + /** Get the title of the wizard. + * @return A human-readable, localized title that should be displayed + * in any dialog showing this wizard + */ + public String getTitle(); + + /** Determine if all navigation buttons should be disabled - if the + * wizard is currently doing some kind of progress/background processing + * task that cannot be interrupted. + */ + public boolean isBusy(); + + /** + * Get the forward navigation mode of this wizard. This + * determines whether the Next button, the Finish button or both should + * be enabled, if the problem string returned from getProblem + * is null. If the problem is set to non-null, the UI should disable + * all forward navigation. + *

+ * This method should never return any value but + * MODE_CAN_CONTINUE, MODE_CAN_FINISH or MODE_CAN_CONTINUE_OR_FINISH - + * it is not a mechanism for disabling all forward navigation (return + * non null from getProblem() for that, or true + * from isBusy() to temporarily disable all navigation). + *

+ * On the final step of the wizard, this method should always return + * MODE_CAN_FINISH. + */ + public int getForwardNavigationMode(); + + /** + * Called when the user cancels the wizard. + */ + public boolean cancel(Map settings); +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardObserver.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardObserver.java new file mode 100644 index 000000000..433f0a9a9 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardObserver.java @@ -0,0 +1,40 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +/** + * Observer which can detect changes in the state of a wizard as the + * user proceeds. Only likely to be used by implementations of + * WizardDisplayer. + */ +public interface WizardObserver { + /** + * Called when the number or names of the steps of the + * wizard changes (for example, the user made a choice in one pane which + * affects the flow of subsequent steps). + * @param wizard The wizard whose steps have changed + */ + public void stepsChanged(Wizard wizard); + + /** + * Called when the enablement of the next/previous/finish buttons + * change, or the problem text changes. + * @param wizard The wizard whose navigability has changed + */ + public void navigabilityChanged(Wizard wizard); + + /** + * Called whenever the current step changes. + * + * @param wizard The wizard whose current step has changed + */ + public void selectionChanged(Wizard wizard); +} \ No newline at end of file diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPage.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPage.java new file mode 100644 index 000000000..d5a9f286c --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPage.java @@ -0,0 +1,1179 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +/* + * FixedWizard.java + * + * Created on August 19, 2005, 9:11 PM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.beans.Beans; +import javax.swing.*; +import javax.swing.text.JTextComponent; +import javax.swing.tree.TreePath; +import java.awt.Color; +import java.awt.Component; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * A convenience JPanel subclass that makes it easy to create wizard panels. + * This class provides a number of conveniences: + *

+ * Automatic listening to child components
+ * If you add an editable component (all standard Swing controls are supported) + * to a WizardPage or a child JPanel inside it, + * a listener is automatically attached to it. If user input occurs, the + * following things happen, in order: + *

    + *
  • If the name property of the component has been set, then + * the value from the component (i.e. Boolean for checkboxes, selected item(s) + * for lists/combo boxes/trees, etc.) will automatically be added to the + * wizard settings map, with the component name as the key.
  • + *
  • Regardless of whether the name property is set, + * validateContents() will be called. You can override that method + * to enable/disable the finish button, call setProblem() to + * disable navigation and display a string to the user, etc. + *
+ *

+ * The above behavior can be disabled by passing false to the + * appropriate constructor. In that case, validateContents will + * never be called automatically. + *

+ * If you have custom components that WizardPage will not know how to listen + * to automatically, attach an appropriate listener to them and optionally + * call userInputReceived() with the component and the event if + * you want to run your automatic validation code. + *

+ * For convenience, this class implements the relevant methods for accessing + * the WizardController and the settings map for the wizard that + * the panel is a part of. + *

+ * Instances of WizardPage can be returned from a WizardPanelProvider; this + * class also offers two methods for conveniently assembling a wizard: + *

    + *
  • Pass an array of already instantiated WizardPages to + * createWizard(). Note that for large wizards, it is preferable + * to construct the panels on demand rather than at construction time.
  • + *
  • Construct a wizard out of WizardPages, instantiating the panels as + * needed: Pass an array of classes all of which + *
      + *
    • Are subclasses of WizardPage
    • + *
    • Have a static method with the following signature: + *
        + *
      • public static String getDescription()
      • + *
      + *
    • + *
    + *
+ *

+ * Note that during development of a wizard, it is worthwhile to test/run with + * assertions enabled, as there is quite a bit of validity checking via assertions + * that can help find problems early. + *

Using Custom Components

+ * If the autoListen constructor argument is true, a WizardPage + * will automatically listen to components which have a name, if they are + * standard Swing components it knows how to listen to. If you are using + * custom components, implement WizardPage.CustomComponentListener and return + * it from createCustomComponentListener() to add supplementary + * listening code for custom components. + *

+ * Note: Swing components do not fire property changes when setName() is called. + * If your component's values are not being propagated into the settings map, + * make sure you are calling setName() before adding the component + * to the hierarchy. + *

+ * Also note that cell editors in tables and lists and so forth are always + * ignored by the automatic listening code. + * + * @author Tim Boudreau + */ +public class WizardPage extends JPanel implements WizardPanel { + + private static final Logger logger = + Logger.getLogger(WizardPage.class.getName()); + + private final String description; + String id; + + //Have an initial dummy map so it's never null. We'll dump its contents + //into the real map the first time it's set + private Map wizardData; + //An initial wizardController that will dump its settings into the real + //one the first time it's set + private WizardControllerImplementation wc = new WC(); + private WizardController controller = new WizardController(wc); + + //Flag to make sure we don't reenter userInputReceieved from maybeUpdateMap() + private boolean inBeginUIChanged = false; + //Flag to make sure we don't reenter userInputReceived because the + //implementation of validateContents changed a component's value, triggering + //a new event on GenericListener + private boolean inUiChanged = false; + private CustomComponentListener ccl; + private boolean autoListen; + + /** + * Create a WizardPage with the passed description and auto-listening + * behavior. + * + * @param stepDescription the localized description of this step + * @param autoListen if true, components added will automatically be + * listened to for user input + */ + public WizardPage(String stepDescription, boolean autoListen) { + this (null, stepDescription, autoListen); + } + /** + * Construct a new WizardPage with the passed step id and description. + * Use this constructor for WizardPages which will be constructed ahead + * of time and passed in an array to createWizard. + * + * @param stepId the unique ID for the step represented. If null, + * the class name or a variant of it will be used + * @param stepDescription the localized description of this step + * @param autoListen if true, components added will automatically be + * listened to for user input + * @see #validateContents + */ + public WizardPage(String stepId, String stepDescription, boolean autoListen) { + id = stepId == null ? getClass().getName() : stepId; + this.autoListen = autoListen; + description = stepDescription; + + } + + private boolean listening; + private void startListening() { + listening = true; + if (autoListen) { + //It will attach itself + GenericListener gl = new GenericListener(this, ccl = createCustomComponentListener(), + ccl == null ? null : new CustomComponentNotifierImpl(this)); + gl.attachToHierarchyOf(this); + } else { + if ((ccl = createCustomComponentListener()) != null) { + throw new IllegalStateException ("CustomComponentListener " + + "will never be called if the autoListen parameter is " + + "false"); + } + } +// if (getClass() == WizardPage.class && stepId == null || +// description == null) { +// throw new NullPointerException ("Step or ID is null"); //NOI18N +// } + setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); //XXX + } + + /** + * Create an auto-listening WizardPage with the passed description + * @param stepDescription the localized description of this step + */ + public WizardPage (String stepDescription) { + this (null, stepDescription); + } + + /** + * Create an auto-listening WizardPage with the passed description + * @param stepId The unique id for the step. If null, an id will be + * generated + * @param stepDescription the localized description of this step + * + */ + public WizardPage(String stepId, String stepDescription) { + this(stepId, stepDescription, true); + } + + /** + * Use this constructor or the default constructor if you intend to + * pass an array of Class objects to lazily create WizardPanels. + */ + protected WizardPage(boolean autoListen) { + this(null, null, autoListen); + } + + /** + * Default constructor. AutoListening will be on by default. + */ + protected WizardPage() { + this(true); + } + + /** + * If you are using custom Swing or AWT components which the + * WizardPage will not know how to automatically listen to, you + * may want to override this method, implement CustomComponentListener + * and return an instance of it. + * @return A CustomComponentListener implementation, or null (the default). + */ + protected CustomComponentListener createCustomComponentListener() { + return null; + } + + /** + * Implement this class if you are using custom Swing or AWT components, + * and return an instance of it from + * WizardPage.createCustomComponentListener(). + */ + public static abstract class CustomComponentListener { + /** + * Indicates that this CustomComponentListener will take responsibility + * for noticing events from the passed component, and that the + * WizardPage should not try to automatically listen on it (which it + * can only do for standard Swing components and their children). + *

+ * Note that this method may be called frequently and any test it + * does should be fast. + *

+ * Important: The return value from this method should always + * be the same for any given component, for the lifetime of the + * WizardPage. + * + * @param c A component + * @return Whether or not this CustomComponentListener will listen + * on the passed component. If true, the component will later be + * passed to startListeningTo() + */ + public abstract boolean accept (Component c); + /** + * Begin listening for events on the component. When an event occurs, + * call the eventOccurred() method on the passed + * CustomComponentNotifier. + * @param c The component to start listening to + * @param n An object that can be called to update the settings map + * when an interesting event occurs on the component + */ + public abstract void startListeningTo (Component c, CustomComponentNotifier n); + /** + * Stop listening for events on a component. + * @param c The component to stop listening to + */ + public abstract void stopListeningTo (Component c); + /** + * Determine if the passed component is a container whose children + * may need to be listened on. Returns false by default. + * + * @param c A component which might be a container + */ + public boolean isContainer(Component c) { + return false; + } + /** + * Get the map key for this component's value. By default, returns + * the component's name. Will only + * be passed components which the accept() method + * returned true for. + *

+ * Important: The return value from this method should always + * be the same for any given component, for the lifetime of the + * WizardPage. + * @param c the component, which the accept method earlier returned + * true for + * @return A string key that should be used in the Wizard's settings + * map for the name of this component's value + */ + public String keyFor (Component c) { + return c.getName(); + } + /** + * Get the value currently set on the passed component. Will only + * be passed components which the accept() method + * returned true for, and which keyFor() returned non-null. + * @param c the component + * @return An object representing the current value of this component. + * For example, if it were a JTextComponent, the value would likely + * be the return value of JTextComponent.getText() + */ + public abstract Object valueFor (Component c); + } + + /** + * Object which is passed to CustomComponentListener.startListeningTo(), + * which can be called when an event has occurred on a custom component the + * CustomComponentListener has claimed (by returning true + * from its accept() method). + */ + public static abstract class CustomComponentNotifier { + private CustomComponentNotifier() {} + /** + * Method which may be called when an event occurred on a custom component. + * @param c the component + * @param eventObject the event object from the component, or null (with + * the exception of javax.swing.text.DocumentEvent, it + * will likely be a subclass of java.util.EventObject). + */ + public abstract void userInputReceived (Component c, Object eventObject); + } + + private static final class CustomComponentNotifierImpl extends CustomComponentNotifier { + private final WizardPage page; + private CustomComponentNotifierImpl (WizardPage page) { + this.page = page; //Slightly smaller footprint a nested, not inner class + } + + public void userInputReceived(Component c, Object event) { + if (!page.ccl.accept(c)) { + return; + } + page.userInputReceived (c, event); + } + } + + String id() { + return getID(); + } + + String description() { + return getDescription(); + } + + private String getID() { + return id; + } + + private String getDescription() { + return description; + } + + public void addNotify() { + super.addNotify(); + if (!listening) { + startListening(); + } + + renderingPage(); + inValidateContents = true; + try { + setProblem(validateContents(null, null)); + } finally { + inValidateContents = false; + } + } + + public WizardPanelNavResult allowBack(String stepName, Map settings, Wizard wizard) { + return WizardPanelNavResult.PROCEED; + } + + public WizardPanelNavResult allowFinish(String stepName, Map settings, Wizard wizard) { + return WizardPanelNavResult.PROCEED; + } + + public WizardPanelNavResult allowNext(String stepName, Map settings, Wizard wizard) { + return WizardPanelNavResult.PROCEED; + } + + private boolean inValidateContents = false; + + /** + * Called whenever the page is rendered. + * This can be used by the page as a notification + * to load page-specific information in its fields. + *

+ * By default, this method does nothing. + */ + protected void renderingPage() { + // Empty + } + + /** + * Create a simple Wizard from an array of WizardPages + */ + public static Wizard createWizard(WizardPage[] contents, WizardResultProducer finisher) { + return new WPP(contents, finisher).createWizard(); + } + + public static Wizard createWizard(String title, WizardPage[] contents, WizardResultProducer finisher) { + return new WPP(title, contents, finisher).createWizard(); + } + + public static Wizard createWizard(String title, WizardPage[] contents) { + return createWizard(title, contents, WizardResultProducer.NO_OP); + } + + /** + * Create a simple Wizard from an array of WizardPages, with a + * no-op WizardResultProducer. + */ + public static Wizard createWizard(WizardPage[] contents) { + return createWizard(contents, WizardResultProducer.NO_OP); + } + + /** + * Create simple Wizard from an array of classes, each of which is a + * unique subclass of WizardPage. + */ + public static Wizard createWizard(Class[] wizardPageClasses, WizardResultProducer finisher) { + return new CWPP(wizardPageClasses, finisher).createWizard(); + } + + /** + * Create simple Wizard from an array of classes, each of which is a + * unique subclass of WizardPage. + */ + public static Wizard createWizard(String title, Class[] wizardPageClasses, WizardResultProducer finisher) { + return new CWPP(title, wizardPageClasses, finisher).createWizard(); + } + + /** + * Create simple Wizard from an array of classes, each of which is a + * unique subclass of WizardPage. + */ + public static Wizard createWizard(String title, Class[] wizardPageClasses) { + return new CWPP(title, wizardPageClasses, + WizardResultProducer.NO_OP).createWizard(); + } + /** + * Create a simple Wizard from an array of classes, each of which is a + * unique subclass of WizardPage, with a + * no-op WizardResultProducer. + */ + public static Wizard createWizard(Class[] wizardPageClasses) { + return createWizard(wizardPageClasses, WizardResultProducer.NO_OP); + } + + /** + * Called by createPanelForStep, with whatever map is passed. In the + * current impl this is always the same Map, but that is not guaranteed. + * If any content was added by calls to putWizardData() during the + * constructor, etc., such data is copied to the settings map the first + * time this method is called. + * + * Subclasses do NOT need to override this method, + * they can override renderPage which is always called AFTER the map has been made valid. + */ + void setWizardDataMap(Map m) { + if (m == null) { + wizardData = new HashMap(); + } else { + if (wizardData instanceof HashMap) { + // our initial map has keys for all of our components + // but with dummy empty values + // So make sure we don't override data that was put in as part of the initialProperties + for (Iterator iter = wizardData.entrySet().iterator(); iter.hasNext();) + { + Map.Entry entry = (Map.Entry) iter.next(); + Object key = entry.getKey(); + if ( ! m.containsKey(key)) + { + m.put(key, entry.getValue()); + } + } + } + wizardData = m; + } + } + + /** + * Set the WizardController. In the current impl, this is always the same + * object, but the API does not guarantee that. The first time this is + * called, it will update the state of the passed controller to match + * any state that was set by components during the construction of this + * component + */ + void setController(WizardController controller) { + if (controller.getImpl() instanceof WC) { + ((WC) controller.getImpl()).configure(controller); + } + + this.controller = controller; + } + + /** + * Get the WizardController for interacting with the Wizard that + * contains this panel. + * Return value will never be null. + */ + private WizardController getController() { + return controller; + } + + + /** + * Set the problem string. Call this method if next/finish should be + * disabled. The passed string will be visible to the user, and should + * be a short, localized description of what is wrong. + */ + protected final void setProblem(String value) { + getController().setProblem(value); + } + + /** + * Set whether the finish, next or both buttons should be enabled, + * assuming no problem string is set. + * + * @param value WizardController.MODE_CAN_CONTINUE, + * WizardController.MODE_CAN_FINISH or + * WizardController.MODE_CAN_CONTINUE_OR_FINISH; + */ + protected final void setForwardNavigationMode(int value) { + getController().setForwardNavigationMode(value); + } + + /** + * Disable all navigation. Useful if some background task is being + * completed during which no navigation should be allowed. Use with care, + * as it disables the cancel button as well. + */ + protected final void setBusy(boolean busy) { + getController().setBusy(busy); + } + + /** + * Store a value in response to user interaction with a GUI component. + */ + protected final void putWizardData(Object key, Object value) { + logger.fine("putWizardData " + key + "=" + value); //NOI18N + getWizardDataMap().put(key, value); + if (!inBeginUIChanged && !inValidateContents) { + inValidateContents = true; + try { + setProblem(validateContents(null, null)); + } finally { + inValidateContents = false; + } + } + } + + /** + * Returns all of the keys in the wizard data map. + */ + protected final Object[] getWizardDataKeys() { + return getWizardDataMap().keySet().toArray(); + } + + /** + * Retrieve a value stored in the wizard map, which may have been + * putWizardData there by this panel or any previous panel in the wizard which + * contains this panel. + */ + protected final Object getWizardData(Object key) { + return getWizardDataMap().get(key); + } + + /** + * Determine if the wizard map contains the requested key. + */ + protected final boolean wizardDataContainsKey(Object key) { + return getWizardDataMap().containsKey(key); + } + + /** + * Called when an event is received from one of the components in the + * panel that indicates user input. Typically you won't need to touch this + * method, unless your panel contains custom components which are not + * subclasses of any standard Swing component, which the framework won't + * know how to listen for changes on. For such cases, attach a listener + * to the custom component, and call this method with the event if you want + * validation to run when input happens. Automatic updating of the + * settings map will not work for such custom components, for obvious + * reasons, so update the settings map, if needed, in validateContents + * for this case. + * + * @param source The component that the user interacted with (if it can + * be determined from the event) or null + * @param event Usually an instance of EventObject, except in the case of + * DocumentEvent. + */ + protected final void userInputReceived(Component source, Object event) { + if (inBeginUIChanged) { + logger.fine("Ignoring recursive entry to userInputReceived while updating map"); + return; + } + + //Update the map no matter what + inBeginUIChanged = true; + + if (source != null) { + try { + maybeUpdateMap(source); + } finally { + inBeginUIChanged = false; + } + } + + //Possibly some programmatic change from checkState could cause + //a recursive call + if (inUiChanged) { + logger.fine("Ignoring recursive entry to userInputReceieved from validateContents"); + return; + } + + inUiChanged = true; + inValidateContents = true; + try { + setProblem(validateContents(source, event)); + } finally { + inUiChanged = false; + inValidateContents = false; + } + } + + /** + * Puts the value from the component in the settings map if the + * component's name property is not null + */ + void maybeUpdateMap(Component comp) { + if (logger.isLoggable(Level.FINE)) { + logger.fine("Maybe update map for " + comp.getClass().getName() + //NOI18N + " named " + comp.getName()); //NOI18N + } + + Object mapKey = getMapKeyFor(comp); + // debug: System.err.println("MaybeUpdateMap " + mapKey + " from " + comp); + if (mapKey != null) { + Object value = valueFrom(comp); + if (logger.isLoggable(Level.FINE)) { + logger.fine("maybeUpdateMap putting " + mapKey + "," + value + + " into settings"); //NOI18N + } + putWizardData(mapKey, value); + } + } + + /** + * Callback for GenericListener to remove a component's value if its name + * changes or it is removed from the panel. + */ + void removeFromMap(Object key) { + logger.fine("removeFromMap: " + key); //NOI18N + getWizardDataMap().remove(key); + } + + /** + * Given an ad-hoc swing component, fetch the likely value based on its + * state. The default implementation handles most common swing components. + * If you are using custom components and have assigned them names, override + * this method to handle getting an appropriate value out of your + * custom component and call super for the others. + */ + protected Object valueFrom(Component comp) { + if (ccl != null && ccl.accept(comp)) { + return ccl.valueFor(comp); + } + if (comp instanceof JRadioButton || comp instanceof JCheckBox || comp instanceof JToggleButton) { + return ((AbstractButton) comp).getModel().isSelected() ? Boolean.TRUE : Boolean.FALSE; + } else if (comp instanceof JTree) { + TreePath path = ((JTree) comp).getSelectionPath(); + if (path != null) { + return path.getLastPathComponent(); + } + } else if (comp instanceof JFormattedTextField) { + return ((JFormattedTextField) comp).getValue(); + } else if (comp instanceof JList) { + Object[] o = ((JList) comp).getSelectedValues(); + if (o != null) { + if (o.length > 1) { + return o; + } else if (o.length == 1) { + return o[0]; + } + } + } else if (comp instanceof JTextComponent) { + return ((JTextComponent) comp).getText(); + } else if (comp instanceof JComboBox) { + return ((JComboBox) comp).getSelectedItem(); + } else if (comp instanceof JColorChooser) { + return ((JColorChooser) comp).getSelectionModel().getSelectedColor(); + } else if (comp instanceof JSpinner) { + return ((JSpinner) comp).getValue(); + } else if (comp instanceof JSlider) { + return new Integer(((JSlider) comp).getValue()); + } + + return null; + } + + /** + * Given an ad-hoc swing component, set the value as the property + * from the settings. The default implementation handles most common swing components. + * If you are using custom components and have assigned them names, override + * this method to handle getting an appropriate value out of your + * custom component and call super for the others. + */ + protected void valueTo(Map settings, Component comp) { + String name = comp.getName(); + Object value = settings.get(name); + if (comp instanceof JRadioButton || comp instanceof JCheckBox || comp instanceof JToggleButton) { + if (value instanceof Boolean) + { + ((AbstractButton) comp).getModel().setSelected(((Boolean) value).booleanValue()); + } +// TOFIX: JTree + } else if (comp instanceof JFormattedTextField) { + ((JFormattedTextField) comp).setValue(value); +// } else if (comp instanceof JTree) { +// TreePath path = ((JTree) comp).getSelectionPath(); +// if (path != null) { +// return path.getLastPathComponent(); +// } + } else if (comp instanceof JList) { + if (value instanceof Object[]) + { + throw new IllegalArgumentException ("can't handle multi-select lists"); + } + ((JList) comp).setSelectedValue(value, true); + } else if (comp instanceof JTextComponent) { + ((JTextComponent) comp).setText((String) value); + } else if (comp instanceof JComboBox) { + ((JComboBox) comp).setSelectedItem(value); + } else if (comp instanceof JColorChooser) { + ((JColorChooser) comp).getSelectionModel().setSelectedColor((Color)value); + } else if (comp instanceof JSpinner) { + ((JSpinner) comp).setValue(value); + } else if (comp instanceof JSlider) { + ((JSlider) comp).setValue(((Integer)value).intValue()); + } + } + + /** + * Get the map key that should be used to automatically put the value + * represented by this component into the wizard data map. + *

+ * The default implementation returns the result of c.getName(), + * which is almost always sufficient and convenient - just set the + * component names in a GUI builder and everything will be handled. + * + * @return null if the component's value should not be automatically + * written to the wizard data map, or an object which is the key that + * later code will use to find this value. By default, it returns the + * component's name. + */ + protected Object getMapKeyFor(Component c) { + if (ccl != null && ccl.accept(c)) { + return ccl.keyFor(c); + } else { + return c.getName(); + } + } + + /** + * Called when user interaction has occurred on a component contained by this + * panel or one of its children. Override this method to check if all of + * the values are legal, such that the Next/Finish button should be enabled, + * optionally calling setForwardNavigationMode() if warranted. + *

+ * This method also may be called with a null argument an effect of + * calling putWizardData() from someplace other than within + * this method. + *

+ * Note that this method may be called very frequently, so it is important + * that validation code be fast. For cases such as DocumentEvents, + * it may be desirable to delay validation with a timer, if the implementation + * of this method is too expensive to call on each keystroke. + *

+ * Either the component, or the event, or both may be null on some calls + * to this method (such as when it is called because the settings map + * has been written to). + *

+ * The default implementation returns null. + * + * @param component The component the user interacted with, if it can be + * determined. The infrastructure does track the owners of list models + * and such, and can find the associated component, so this will usually + * (but not necessarily) be non-null. + * @param event The event object (if any) that triggered this call to + * validateContents. For most cases this will be an instance of + * EventObject, and can be used to directly detect what component + * the user interacted with. Since javax.swing.text.DocumentEvent is + * not a subclass of EventObject, the type of the argument is Object, + * so these events may be passed. + * @return A localized string describing why navigation should be disabled, + * or null if the state of the components is valid and forward navigation + * should be enabled. + */ + protected String validateContents(Component component, Object event) { + return null; + } + + /** + * Called if the user is navigating into this panel when it has already + * been displayed at least once - the user has navigated back to this + * panel, or back past this panel and is now navigating forward again. + *

+ * If some of the UI needs to be set up based on values from earlier + * pages that may have changed, do that here, fetching values from the + * settings map by calling getWizardData(). + *

+ * The default implementation simply calls + * validateContents (null, null). + */ + protected void recycle() { + setProblem(validateContents(null, null)); + } + + /** + * Get the settings map into which the wizard gathers settings. + * Return value will never be null. + */ + // the map is empty during construction, then later set to the map from the containing WizardController + protected Map getWizardDataMap() { + if (wizardData == null) { + wizardData = new HashMap(); + } + return wizardData; + } + + private String longDescription; + /** + * Set the long description of this page. This method may be called + * only once and should be called from within the constructor. + * @param desc The long description for this step + */ + protected void setLongDescription(String desc) { + if (!Beans.isDesignTime() && this.longDescription != null) { + throw new IllegalStateException ("Long description already set to" + + " " + desc); + } + this.longDescription = desc; + } + + /** + * Get the long description of this page, which should be used in the title + * area of the wizard's UI if non-null. To use, call setLongDescription() + * in your WizardPage's constructor. It may be set only once. + * + * @return the description + */ + public final String getLongDescription() { + return longDescription; + } + + static WizardPanelProvider createWizardPanelProvider (WizardPage page) { + return new WPP (new WizardPage[] { page }, WizardResultProducer.NO_OP); + } + + static WizardPanelProvider createWizardPanelProvider (WizardPage[] page) { + return new WPP (page, WizardResultProducer.NO_OP); + } + + + /** + * WizardPanelProvider that takes an array of already created WizardPages + */ + static final class WPP extends WizardPanelProvider { + private final WizardPage[] pages; + private final WizardResultProducer finish; + + WPP(WizardPage[] pages, WizardResultProducer finish) { + super(Util.getSteps(pages), Util.getDescriptions(pages)); + + //Fail-fast validation - don't wait until something goes wrong + //if the data are bad + // assert valid(pages) == null : valid(pages); + // assert finish != null; + String v = valid(pages); + if (v != null) + { + throw new RuntimeException (v); + } + if (finish == null) + { + throw new RuntimeException ("finish must not be null"); + } + + this.pages = pages; + this.finish = finish; + } + + WPP(String title, WizardPage[] pages, WizardResultProducer finish) { + super(title, Util.getSteps(pages), Util.getDescriptions(pages)); + + //Fail-fast validation - don't wait until something goes wrong + //if the data are bad + // assert valid(pages) == null : valid(pages); + // assert finish != null; + String v = valid(pages); + if (v != null) + { + throw new RuntimeException (v); + } + if (finish == null) + { + throw new RuntimeException ("finish must not be null"); + } + + + this.pages = pages; + this.finish = finish; + } + + protected JComponent createPanel(WizardController controller, String id, + Map wizardData) { + int idx = indexOfStep(id); + + // assert idx != -1 : "Bad ID passed to createPanel: " + id; //NOI18N + if (idx == -1) + { + throw new RuntimeException ("Bad ID passed to createPanel: " + id); //NOI18N + } + pages[idx].setController(controller); + pages[idx].setWizardDataMap(wizardData); + + return pages[idx]; + } + + /** + * Make sure we haven't been passed bogus data + */ + private String valid(WizardPage[] pages) { + if (new HashSet(Arrays.asList(pages)).size() != pages.length) { + return "Duplicate entry in array: " + //NOI18N + Arrays.asList(pages); + } + + for (int i = 0; i < pages.length; i++) { + if (pages[i] == null) { + return "Null entry at " + i + " in pages array"; //NOI18N + } + } + + return null; + } + + protected Object finish(Map settings) throws WizardException { + return finish.finish(settings); + } + + public boolean cancel(Map settings) { + return finish.cancel (settings); + } + + public String getLongDescription(String stepId) { + for (int i = 0; i < pages.length; i++) { + WizardPage wizardPage = pages[i]; + if (stepId.equals(wizardPage.getID())) { + return wizardPage.getLongDescription(); + } + } + return null; + } + } + + /** + * WizardPanelProvider that takes an array of WizardPage subclasses and + * instantiates them on demand + */ + private static final class CWPP extends WizardPanelProvider { + private final Class[] classes; + private final WizardResultProducer finish; + private final String[] longDescriptions; + + CWPP(String title, Class[] classes, WizardResultProducer finish) { + super(title, Util.getSteps(classes), Util.getDescriptions(classes)); +// assert classes != null : "Class array may not be null"; +// assert new HashSet(Arrays.asList(classes)).size() == classes.length : +// "Duplicate entries in class array"; +// assert finish != null : "WizardResultProducer may not be null"; + + _validateArgs (classes, finish); + this.finish = finish; + this.classes = classes; + longDescriptions = new String [ classes.length ]; + } + + private void _validateArgs (Class [] classes, WizardResultProducer finish) + { +// assert classes != null : "Class array may not be null"; +// assert new HashSet(Arrays.asList(classes)).size() == classes.length : +// "Duplicate entries in class array"; +// assert finish != null : "WizardResultProducer may not be null"; + + if (classes == null) + { + throw new RuntimeException ("Class array may not be null"); + } + if ( new HashSet(Arrays.asList(classes)).size() != classes.length) + { + throw new RuntimeException ("Duplicate entries in class array"); + } + if (finish == null) + { + throw new RuntimeException ("WizardResultProducer may not be null"); + } + } + + CWPP(Class[] classes, WizardResultProducer finish) { + super(Util.getSteps(classes), Util.getDescriptions(classes)); + +// assert classes != null : "Class array may not be null"; +// assert new HashSet(Arrays.asList(classes)).size() == classes.length : +// "Duplicate entries in class array"; +// assert finish != null : "WizardResultProducer may not be null"; + + longDescriptions = new String [ classes.length ]; + _validateArgs (classes, finish); + + this.classes = classes; + this.finish = finish; + } + + + protected JComponent createPanel(WizardController controller, String id, Map wizardData) { + int idx = indexOfStep(id); + + // assert idx != -1 : "Bad ID passed to createPanel: " + id; //NOI18N + if (idx == -1) + { + throw new RuntimeException ( "Bad ID passed to createPanel: " + id); //NOI18N + } + try { + WizardPage result = (WizardPage) classes[idx].newInstance(); + longDescriptions[idx] = result.getLongDescription(); + + result.setController(controller); + result.setWizardDataMap(wizardData); + + return result; + } catch (Exception e) { + logger.log(Level.WARNING, "Could not instantiate " + classes[idx], e); + // really IllegalArgumentException, but we need to have the "cause" get shown in stack trace + throw new RuntimeException("Could not instantiate " + //NOI18N + classes[idx], e); + } + } + + protected Object finish(Map settings) throws WizardException { + return finish.finish(settings); + } + + public boolean cancel(Map settings) { + return finish.cancel(settings); + } + + public String toString() { + return super.toString() + " for " + finish; + } + + public String getLongDescription(String stepId) { + int idx = indexOfStep (stepId); + if (idx != -1) { + return longDescriptions[idx] == null ? descriptions [idx] : + longDescriptions[idx]; + } + return null; + } + } + + /** + * A dummy wizard controller which is used until the panel has actually + * been put into use; so state can be set during the constructor, etc. + * Its state will be dumped into the real one once there is a real one. + */ + private static final class WC implements WizardControllerImplementation { + private String problem = null; + private int canFinish = -1; + private Boolean busy = null; + + public void setProblem(String value) { + this.problem = value; + } + + public void setForwardNavigationMode(int value) { + switch (value) { + case WizardController.MODE_CAN_CONTINUE : + case WizardController.MODE_CAN_FINISH : + case WizardController.MODE_CAN_CONTINUE_OR_FINISH : + break; + default : + throw new IllegalArgumentException(Integer.toString(value)); + } + + canFinish = value; + } + + public void setBusy(boolean busy) { + this.busy = busy ? Boolean.TRUE : Boolean.FALSE; + } + + void configure(WizardController other) { + if (other == null) { + return; + } + + if (busy != null) { + other.setBusy(busy.booleanValue()); + } + + if (canFinish != -1) { + other.setForwardNavigationMode(canFinish); + } + + if (problem != null) { + other.setProblem(problem); + } + } + } + + /** + * Interface that is passed to WizardPage.createWizard(). For wizards + * created from a set of WizardPages or WizardPage subclasses, this is + * the object that whose code will be run to create or do whatever the + * wizard does when the user clicks the Finish button. + */ + public static interface WizardResultProducer { + /** + * Conclude a wizard, doing whatever the wizard does with the data + * gathered into the map on the various panels. + *

+ * If an instance of Summary is returned from this method, the + * UI shall display it on a final page and disable all navigation buttons + * except the Close/Cancel button. + *

+ * If an instance of DeferredWizardResult is returned from this + * method, the UI shall display some sort of progress bar while the result + * is computed in the background. If that DeferredWizardResult + * produces a Summary object, that summary shall be displayed + * as described above. + * @param wizardData the map with key-value pairs which has been + * populated by the UI as the user progressed through the wizard + * @return an object composed based on what the user entered in the wizard - + * somethingmeaningful to whatever code invoked the wizard, or null. Note + * special handling if an instance of DeferredWizardResult + * or Summary is returned from this method. + */ + Object finish(Map wizardData) throws WizardException; + + /** + * Called when the user presses the cancel button. Almost all + * implementations will want to return true. + */ + boolean cancel(Map settings); + + /** + * A no-op WizardResultProducer that returns null. + */ + WizardResultProducer NO_OP = new WizardResultProducer() { + public Object finish(Map wizardData) { + return wizardData; + } + + public boolean cancel (Map settings) { + return true; + } + + public String toString() { + return "NO_OP WizardResultProducer"; + } + }; + } + +} diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanel.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanel.java new file mode 100644 index 000000000..875cfd586 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanel.java @@ -0,0 +1,89 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +*/ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.util.Map; + +/** + * This is an optional interface for panels that want to be notified when + * the next and back buttons are pressed. + * + * The WizardPanelProvider is NOT required to create panels that implement + * this interface. + * + * Each of these methods returns a WizardPanelNavResult that can be used to + * indicate PROCEED or REMAIN_ON_PAGE. + * + * The result can also be an instance of a subclass of WizardPanelNavResult + * that implements the start method to use a background thread + * to determine if the next page can be shown. + * + * @author stanley@stanleyknutson.com + */ +public interface WizardPanel +{ + /** + * This method is invoked when the "next" button has been pushed, + * to do a final validation of input (such as doing a database login). + * + * If this method return false, then the "next" button will not change the + * displayed panel. Presumably some error will have been shown to the user. + * + * @param stepName + * @param settings + * @param wizard + * @return WizardPanelNavResult.PROCEED if the "next" button should proceed, + * WizardPanelNavResult.REMAIN_ON_PAGE if "next" should not proceed, + * or a instance of subclass of WizardPanelResult that will do some background computation + * and call the progress.finished method with one of those constants + * (or call progress.failed with the error message) + */ + public WizardPanelNavResult allowNext (String stepName, Map settings, Wizard wizard); + + /** + * This method is invoked when the "back" button has been pushed, + * to discard any data from the setings that will not been needed and for which the + * normal "just hide that data" is not the desired behavior. + * (See MergeMap for discussion of the "hide the data" behavior) + * + * If this method return false, then the "next" button will not change the + * displayed panel. Presumably some error will have been shown to the user. + * + * @param stepName + * @param settings + * @param wizard + * @return WizardPanelNavResult.PROCEED if the "back" button should proceed, + * WizardPanelNavResult.REMAIN_ON_PAGE if "back" should not proceed, + * or a instance of subclass of WizardPanelResult that will do some background computation + * and call the progress.finished method with one of those constants. + * (or call progress.failed with the error message) + */ + public WizardPanelNavResult allowBack (String stepName, Map settings, Wizard wizard); + + /** + * This method is invoked when the "finish" button has been pushed, + * to allow veto of the finish action BEFORE the wizard finish method is invoked. + * + * If this method return false, then the "finish" button will have no effect. + * Presumably some error will have been shown to the user. + * + * @param stepName + * @param settings + * @param wizard + * @return WizardPanelNavResult.PROCEED if the "finish" button should proceed, + * WizardPanelNavResult.REMAIN_ON_PAGE if "finish" should not proceed, + * or a instance of subclass of WizardPanelResult that will do some background computation + * and call the progress.finished method with one of those constants. + * (or call progress.failed with the error message) + */ + public WizardPanelNavResult allowFinish (String stepName, Map settings, Wizard wizard); + +} + diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanelNavResult.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanelNavResult.java new file mode 100644 index 000000000..a39c45271 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanelNavResult.java @@ -0,0 +1,89 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +*/ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.util.Map; + +/** + * Result class for the methods in WizardPanel. + * + * For immediate action, one of the two constantants PROCEED or REMAIN_ON_PAGE + * should be returned. Otherwise an instance of a subclass should be returned + * that computes a Boolean result. + * + * @author stanley@stanleyknutson.com + */ +public abstract class WizardPanelNavResult extends DeferredWizardResult +{ + /** + * value for procced to next step in the wizard. + */ + public static final WizardPanelNavResult PROCEED = new WPNRimmediate(true); + /** + * Value to remain on the current page in the wizard + */ + public static final WizardPanelNavResult REMAIN_ON_PAGE = new WPNRimmediate(false); + + public WizardPanelNavResult(boolean useBusy) { + super (false, useBusy); + } + + public WizardPanelNavResult(boolean useBusy, boolean canAbort) { + super (canAbort, useBusy); + } + + public WizardPanelNavResult() { + super (false, false); + } + + public boolean isDeferredComputation() + { + return true; + } + + /* + * internal class for the constants only + */ + private final static class WPNRimmediate extends WizardPanelNavResult + { + boolean value; + + WPNRimmediate (boolean v) + { + value = v; + } + public boolean isDeferredComputation() + { + return false; + } + + public boolean equals (Object o) + { + if (o instanceof WPNRimmediate && ((WPNRimmediate)o).value == value) + { + return true; + } + return false; + } + + public int hashCode() + { + return value ? 1 : 2; + } + + public void start(Map settings, ResultProgressHandle progress) + { + // Should never get here, this is supposed to be immediate! + throw new RuntimeException("Immediate result was called as deferral!"); + } + + } +} + diff --git a/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanelProvider.java b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanelProvider.java new file mode 100644 index 000000000..2b08022d3 --- /dev/null +++ b/HMCLAPI/src/main/java/org/jackhuang/hellominecraft/utils/views/wizard/spi/WizardPanelProvider.java @@ -0,0 +1,323 @@ +/* The contents of this file are subject to the terms of the Common Development +and Distribution License (the License). You may not use this file except in +compliance with the License. + You can obtain a copy of the License at http://www.netbeans.org/cddl.html +or http://www.netbeans.org/cddl.txt. + When distributing Covered Code, include this CDDL Header Notice in each file +and include the License file at http://www.netbeans.org/cddl.txt. +If applicable, add the following below the CDDL Header, with the fields +enclosed by brackets [] replaced by your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" */ +/* + * PanelProvider.java + * + * Created on March 5, 2005, 7:25 PM + */ + +package org.jackhuang.hellominecraft.utils.views.wizard.spi; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Map; +import javax.swing.JComponent; + +/** + * (Note: WizardPage offers somewhat simpler functionality for + * creating a wizard than does WizardPanelProvider; the only advantage of + * WizardPanelProvider is that it does not require one to + * subclass a panel component). + *

+ * A simple interface for providing a fixed set of panels for a wizard. + * To use, simply implement createPanel() to create the + * appropriate UI component for a given step (a unique String ID - one of the ones passed + * in the constructor in the steps array), and implement + * finish() to do whatever should be done when the wizard is + * finished. + *

+ * To control whether the Next/Finish buttons are enabled, components + * created in createPanel() should call methods on the + * WizardController passed. The created panels should listen on the + * UI components they create, updating the settings Map when the user changes + * their input. + *

+ * Super-simple one-pane wizard example - if the checkbox is checked, the user + * can continue: + *

+ * public class MyProvider extends WizardPanelProvider {
+ *    public MyProvider() {
+ *       //here we pass a localized title for the wizard, 
+ *       //the ID of the one step it will have, and a localized description
+ *       //the wizard can show for that one step
+ *       super ("Click the box", "click", "Click the checkbox");
+ *    }
+ *    protected JComponent createPanel (final WizardController controller, String id, final Map settings) {
+ *       //A quick sanity check
+ *       assert "click".equals (id);
+ *       //Remember this method will only be called once for any panel
+ *       final JCheckBox result = new JCheckBox();
+ *       result.addActionListener (new ActionListener() {
+ *          public void actionPerformed (ActionEvent ae) {
+ *             //Typically you want to write the result of some user 
+ *             //action into the settings map as soon as they do it 
+ *             settings.put ("boxSelected", result.isSelected() ? Boolean.TRUE : Boolean.FALSE);
+ *             if (result.isSelected()) {
+ *                controller.setProblem(null);
+ *             } else {
+ *                controller.setProblem("The box is not checked");
+ *             }
+ *             controller.setCanFinish(true); //won't matter if we called setProblem() with non-null
+ *          }
+ *       });
+ *       return result;
+ *    }
+ *
+ *    protected Object finish (Map settings) throws WizardException {
+ *       //if we had some interesting information (Strings a user put in a 
+ *       //text field or something, we'd generate some interesting object or
+ *       //create some files or something here
+ *       return null;
+ *    }
+ * }
+ * 
+ * + * @author Tim Boudreau + */ +public abstract class WizardPanelProvider { + final String title; + final String[] descriptions; + final String[] steps; + final String[] knownProblems; + + /** + * Create a WizardPanelProvider. The passed array of steps and descriptions + * will be used as IDs and localized descriptions of the various steps in + * the wizard. Use this constructor (which passes not title) for sub-wizards + * used in a WizardBranchController, where the first pane + * will determine the title, and the titles of the sub-wizards will never be + * shown. + * @param steps A set of unique IDs identifying each step of this wizard. Each + * ID must occur only once in the array of steps. + * + * @param descriptions A set of human-readable descriptions corresponding + * 1:1 with the unique IDs passed as the steps parameter + */ + protected WizardPanelProvider (String[] steps, String[] descriptions) { + this (null, steps, descriptions); + } + + /** + * Create a WizardPanelProvider with the provided title, steps and + * descriptions. The steps parameter are unique IDs of + * panels, which will be passed to createPanel to create + * panels for various steps in the wizard, as the user navigates it. + * The descriptions parameter is a set of localized descriptions + * that can appear in the Wizard to describe each step. + * @param title A human readable title for the wizard dialog + * @param steps An array of unique IDs for the various panels of this + * wizard + * @param descriptions An array of descriptions corresponding 1:1 with the + * unique IDs. These must be human readable, localized strings. + */ + protected WizardPanelProvider (String title, String[] steps, String[] descriptions) { + this.title = title; + this.steps = steps; + this.descriptions = descriptions; + knownProblems = new String[steps.length]; + if (steps.length != descriptions.length) { + throw new IllegalArgumentException ("Length of steps and" + + " descriptions arrays do not match"); + } + // assert validData (steps, descriptions) == null : validData (steps, descriptions); + String v = validData (steps, descriptions); + if (v != null) + { + throw new RuntimeException (v); + } + } + + + private String validData (String[] steps, String[] descriptions) { + if (steps.length != descriptions.length) { + return steps.length + " steps but " + descriptions.length + + " descriptions"; + } + for (int i=0; i < steps.length; i++) { + if (steps[i] == null) { + throw new NullPointerException ("Step id " + i + " is null"); + } + if (descriptions[i] == null) { + throw new NullPointerException ("Description " + i + " is null"); + } + } + if (new HashSet(Arrays.asList(steps)).size() != steps.length) { + return "Duplicate step ids: " + Arrays.asList(steps); + } + return null; + } + + /** + * Convenience constructor to create a WizardPanelProvider which has only + * one step to it. Mainly useful for initial steps in a WizardBranchController. + * @param title A human readable title for the wizard dialog + * @param singleStep The unique ID of the only step this wizard has + * @param singleDescription The human-readable description of what the user + * should do in the one step of this one-step wizard or sub-wizard + */ + protected WizardPanelProvider (String title, String singleStep, String singleDescription) { + this (title, new String[] {singleStep}, new String[] {singleDescription}); + } + + /** + * Create a panel that represents a named step in the wizard. + * This method will be called exactly once in the life of + * a wizard. The panel should retain the passed settings Map, and + * add/remove values from it as the user enters information, calling + * setProblem() and setCanFinish() as + * appropriate in response to user input. + * + * @param controller - the object which controls whether the + * Next/Finish buttons in the wizard are enabled, and what instructions + * are displayed to the user if they are not + * @param id The name of the step, one of the array of steps passed in + * the constructor + * @param settings A Map containing settings from earlier steps in + * the wizard. It is safe to retain a reference to this map and put + * values in it as the user manipulates the UI; the reference should + * be refreshed whenever this method is called again. + * @return A JComponent that should be displayed in the center of the + * wizard + */ + protected abstract JComponent createPanel (WizardController controller, String id, Map settings); + + /** + * Instantiate whatever object (if any) the wizard creates from its + * gathered data. The default implementation is a no-op that returns + * null. + *

+ * If an instance of Summary is returned from this method, the + * UI shall display it on a final page and disable all navigation buttons + * except the Close/Cancel button. + *

+ * If an instance of DeferredWizardResult is returned from this + * method, the UI shall display some sort of progress bar while the result + * is computed in the background. If that DeferredWizardResult + * produces a Summary object, that summary shall be displayed + * as described above. + *

+ * The default implementation returns the settings map it is passed. + * + * @param settings The settings map, now fully populated with all settings needed + * to complete the wizard (this method will only be called if + * setProblem(null) and setCanFinish(true) have + * been called on the WizardController passed to + * createPanel(). + * @return an object composed based on what the user entered in the wizard - + * somethingmeaningful to whatever code invoked the wizard, or null. Note + * special handling if an instance of DeferredWizardResult + * or Summary is returned from this method. + */ + protected Object finish (Map settings) throws WizardException { + return settings; + } + + /** + * The method provides a chance to call setProblem() or setCanFinish() when + * the user re-navigates to a panel they've already seen - in the case + * that the user pressed the Previous button and then the Next button. + *

+ * The default implementation does nothing, which is sufficient for most + * cases. + * If whether this panel is valid or not could + * have changed because of changed data from a previous panel, or it + * displays data entered on previous panes which may have changed, + * you may want to override this method to ensure validity and canFinish + * are set correctly, and that the components have the correct text. + *

+ * This method will not be called when a panel is first instantiated - + * createPanel() is expected to set validity and canFinish + * appropriately. + *

+ * The settings Map passed to this method will always be the same + * Settings map instance that was passed to createPanel() + * when the panel was created. + *

+ * If you are implementing WizardPanelProvider and some of the pages are + * WizardPages, you should call the super implementation if + * you override this method. + */ + protected void recycleExistingPanel (String id, WizardController controller, Map wizardData, JComponent panel) { + //do nothing + } + + void recycle (String id, WizardController controller, Map wizardData, JComponent panel) { + if (panel instanceof WizardPage) { + WizardPage page = (WizardPage) panel; + page.setController(controller); + page.setWizardDataMap(wizardData); + page.recycle(); + } + recycleExistingPanel (id, controller, wizardData, panel); + } + + private Wizard wizard; + /** + * Create a Wizard for this PanelProvider. The instance created by this + * method is cached and the same instance will be returned on subsequent + * calls. + */ + public final Wizard createWizard() { + if (wizard == null) { + wizard = new Wizard (new SimpleWizard (this)); + } + return wizard; + } + + /** + * This method can optionally be overridden to provide a longer + * description of a step to be shown in the top of its panel. + * The default implementation returns null, indicating that the + * short description should be used. + * + * @param stepId a unique id for one step of the wizard + * @return An alternate description for use in the top of the wizard + * page when this page is the current one, or null + */ + public String getLongDescription (String stepId) { + return null; + } + + /** + * Convenience method to get the index into the array of steps passed to + * the constructor of a specific step id. + */ + protected final int indexOfStep (String id) { + return Arrays.asList(steps).indexOf(id); + } + + void setKnownProblem (String problem, int idx) { + //Record a problem message so we can put it back if the user does + //prev and then next + if (idx >= 0) { //setProblem() can be called during initialization + knownProblems[idx] = problem; + } + } + + String getKnownProblem(int idx) { + return knownProblems[idx]; + } + + /** + * Called if the user invokes cancel. The default impl returns + * true. + * @return false to abort cancellation (almost all implementations will + * want to return true - this is really only applicable in cases such + * as an OS installer or such). + */ + public boolean cancel(Map settings) { + return true; + } + + public String toString() { + return super.toString() + " with wizard " + wizard; + } +} diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N.properties index fc91d2e29..2837d2574 100755 --- a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N.properties +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N.properties @@ -208,11 +208,14 @@ settings.custom=\u81ea\u5b9a\u4e49 settings.choose_gamedir=\u9009\u62e9\u6e38\u620f\u8def\u5f84 settings.failed_load=\u8bbe\u7f6e\u6587\u4ef6\u52a0\u8f7d\u5931\u8d25\uff0c\u53ef\u80fd\u662f\u5347\u7ea7\u4e86\u542f\u52a8\u5668\u6216\u88ab\u4eba\u5de5\u4fee\u6539\u9020\u6210\u9519\u8bef\uff0c\u662f\u5426\u6e05\u9664\uff1f +settings.modpack=\u6574\u5408\u5305 settings.modpack.choose=\u9009\u62e9\u8981\u5bfc\u5165\u7684\u6e38\u620f\u6574\u5408\u5305\u6587\u4ef6 +settings.modpack.install.task=\u5bfc\u5165\u6574\u5408\u5305 settings.modpack.install_error=\u5b89\u88c5\u5931\u8d25\uff0c\u53ef\u80fd\u662f\u6574\u5408\u5305\u683c\u5f0f\u4e0d\u6b63\u786e\u6216\u64cd\u4f5c\u6587\u4ef6\u5931\u8d25 settings.modpack.save=\u9009\u62e9\u8981\u5bfc\u51fa\u5230\u7684\u6e38\u620f\u6574\u5408\u5305\u4f4d\u7f6e +settings.modpack.save.task=\u5bfc\u51fa\u6574\u5408\u5305 settings.modpack.export_error=\u5bfc\u51fa\u5931\u8d25\uff0c\u53ef\u80fd\u662f\u60a8\u7684\u6e38\u620f\u6587\u4ef6\u5939\u683c\u5f0f\u4e0d\u6b63\u786e\u6216\u64cd\u4f5c\u6587\u4ef6\u5931\u8d25 -settings.modpack=\u6574\u5408\u5305 +settings.modpack.enter_name=\u7ed9\u6e38\u620f\u8d77\u4e2a\u4f60\u559c\u6b22\u7684\u540d\u5b57 mods=Mod\u7ba1\u7406 mods.choose_mod=\u9009\u62e9\u6a21\u7ec4 @@ -260,6 +263,7 @@ launcher.enable_shadow=\u542f\u7528\u7a97\u53e3\u9634\u5f71(\u91cd\u542f\u542f\u launcher.theme=\u4e3b\u9898 launcher.proxy=\u4ee3\u7406 launcher.decorated=\u542f\u7528\u7a97\u53e3\u8fb9\u6846(Linux\u4e0b\u53ef\u89e3\u51b3\u7a0b\u5e8f\u754c\u9762\u5168\u7070\u95ee\u9898) +launcher.modpack=\u6574\u5408\u5305\u4f5c\u8005\u5e2e\u52a9 launcher.title.game=\u6e38\u620f\u8bbe\u7f6e launcher.title.main=\u4e3b\u9875 @@ -338,4 +342,3 @@ color.green=\u7eff\u8272 color.orange=\u6a59\u8272 color.dark_blue=\u6df1\u84dd\u8272 color.purple=\u7d2b\u8272 -launcher.modpack=\u6574\u5408\u5305\u4f5c\u8005\u5e2e\u52a9 diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_en.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_en.properties index d1d0bc518..1e5a9966e 100755 --- a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_en.properties +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_en.properties @@ -208,11 +208,14 @@ settings.custom=Custom settings.choose_gamedir=Choose Game Directory settings.failed_load=Failed to load settings file. Remove it? +settings.modpack=Mod pack settings.modpack.choose=Choose a modpack zip file which you want to import. +settings.modpack.install.task=Import the modpack settings.modpack.install_error=Failed to install the modpack, maybe the modpack file is incorrect or failed to manage files. settings.modpack.save=Choose a location which you want to export the game files to. +settings.modpack.save.task=Export the modpack settings.modpack.export_error=Failed to export the modpack, maybe the format of your game directory is incorrect or failed to manage files. -settings.modpack=Mod pack +settings.modpack.enter_name=Give this game a name which is your favorite. mods=Mods mods.choose_mod=Choose your mods @@ -260,6 +263,7 @@ launcher.enable_shadow=Enable Window Shadow launcher.theme=Theme launcher.proxy=Proxy launcher.decorated=Enable system window border(in order to fix the problem that the ui become all gray in Linux OS) +launcher.modpack=Documentations for modpacks. launcher.title.game=Games launcher.title.main=Home diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_zh.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_zh.properties index 033a78e64..25596280d 100755 --- a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_zh.properties +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_zh.properties @@ -208,11 +208,14 @@ settings.custom=\u81ea\u5b9a\u7fa9 settings.choose_gamedir=\u9009\u62e9\u6e38\u620f\u8def\u5f84 settings.failed_load=\u8a2d\u5b9a\u6587\u4ef6\u52a0\u8f09\u5931\u6557\uff0c\u53ef\u80fd\u662f\u5347\u7d1a\u4e86\u555f\u52d5\u5668\u6216\u88ab\u4eba\u5de5\u4fee\u6539\u9020\u6210\u932f\u8aa4\uff0c\u662f\u5426\u6e05\u9664\uff1f -settings.modpack.choose=\u9078\u64c7\u8981\u5c0e\u5165\u7684\u904a\u6232\u6574\u5408\u5305\u6587\u4ef6 -settings.modpack.install_error=\u5b89\u88dd\u5931\u6557\uff0c\u53ef\u80fd\u662f\u6574\u5408\u5305\u683c\u5f0f\u4e0d\u6b63\u78ba\u6216\u64cd\u4f5c\u6587\u4ef6\u5931\u6557 -settings.modpack.save=\u9078\u64c7\u8981\u5c0e\u51fa\u5230\u7684\u904a\u6232\u6574\u5408\u5305\u4f4d\u7f6e -settings.modpack.export_error=\u5c0e\u51fa\u5931\u6557\uff0c\u53ef\u80fd\u662f\u60a8\u7684\u904a\u6232\u6587\u4ef6\u593e\u683c\u5f0f\u4e0d\u6b63\u78ba\u6216\u64cd\u4f5c\u6587\u4ef6\u5931\u6557 settings.modpack=\u61f6\u4eba\u5305 +settings.modpack.choose=\u9078\u64c7\u8981\u5c0e\u5165\u7684\u904a\u6232\u61f6\u4eba\u5305\u6587\u4ef6 +settings.modpack.install.task=\u5c0e\u5165\u61f6\u4eba\u5305 +settings.modpack.install_error=\u5b89\u88dd\u5931\u6557\uff0c\u53ef\u80fd\u662f\u6574\u5408\u5305\u683c\u5f0f\u4e0d\u6b63\u78ba\u6216\u64cd\u4f5c\u6587\u4ef6\u5931\u6557 +settings.modpack.save=\u9078\u64c7\u8981\u5c0e\u51fa\u5230\u7684\u904a\u6232\u61f6\u4eba\u5305\u4f4d\u7f6e +settings.modpack.save.task=\u5c0e\u51fa\u61f6\u4eba\u5305 +settings.modpack.export_error=\u5c0e\u51fa\u5931\u6557\uff0c\u53ef\u80fd\u662f\u60a8\u7684\u904a\u6232\u6587\u4ef6\u593e\u683c\u5f0f\u4e0d\u6b63\u78ba\u6216\u64cd\u4f5c\u6587\u4ef6\u5931\u6557 +settings.modpack.enter_name=\u7d66\u904a\u6232\u8d77\u500b\u4f60\u559c\u6b61\u7684\u540d\u5b57 mods=Mod\u7ba1\u7406 mods.choose_mod=\u9009\u62e9\u6a21\u7ec4 @@ -260,6 +263,7 @@ launcher.enable_shadow=\u542f\u7528\u7a97\u53e3\u9634\u5f71(\u91cd\u542f\u542f\u launcher.theme=\u4e3b\u9898 launcher.proxy=\u4ee3\u7406 launcher.decorated=\u555f\u7528\u7a97\u53e3\u908a\u6846(Linux\u4e0b\u53ef\u89e3\u6c7a\u7a0b\u5e8f\u754c\u9762\u5168\u7070\u554f\u984c) +launcher.modpack=\u6574\u5408\u5305\u4f5c\u8005\u5e2e\u52a9 launcher.title.game=\u904a\u6232\u8a2d\u5b9a launcher.title.main=\u4e3b\u9801 diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_zh_CN.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_zh_CN.properties index c414c0d03..23f398a7d 100755 --- a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_zh_CN.properties +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/launcher/I18N_zh_CN.properties @@ -208,11 +208,14 @@ settings.custom=\u81ea\u5b9a\u4e49 settings.choose_gamedir=\u9009\u62e9\u6e38\u620f\u8def\u5f84 settings.failed_load=\u8bbe\u7f6e\u6587\u4ef6\u52a0\u8f7d\u5931\u8d25\uff0c\u53ef\u80fd\u662f\u5347\u7ea7\u4e86\u542f\u52a8\u5668\u6216\u88ab\u4eba\u5de5\u4fee\u6539\u9020\u6210\u9519\u8bef\uff0c\u662f\u5426\u6e05\u9664\uff1f +settings.modpack=\u6574\u5408\u5305 settings.modpack.choose=\u9009\u62e9\u8981\u5bfc\u5165\u7684\u6e38\u620f\u6574\u5408\u5305\u6587\u4ef6 +settings.modpack.install.task=\u5bfc\u5165\u6574\u5408\u5305 settings.modpack.install_error=\u5b89\u88c5\u5931\u8d25\uff0c\u53ef\u80fd\u662f\u6574\u5408\u5305\u683c\u5f0f\u4e0d\u6b63\u786e\u6216\u64cd\u4f5c\u6587\u4ef6\u5931\u8d25 settings.modpack.save=\u9009\u62e9\u8981\u5bfc\u51fa\u5230\u7684\u6e38\u620f\u6574\u5408\u5305\u4f4d\u7f6e +settings.modpack.save.task=\u5bfc\u51fa\u6574\u5408\u5305 settings.modpack.export_error=\u5bfc\u51fa\u5931\u8d25\uff0c\u53ef\u80fd\u662f\u60a8\u7684\u6e38\u620f\u6587\u4ef6\u5939\u683c\u5f0f\u4e0d\u6b63\u786e\u6216\u64cd\u4f5c\u6587\u4ef6\u5931\u8d25 -settings.modpack=\u6574\u5408\u5305 +settings.modpack.enter_name=\u7ed9\u6e38\u620f\u8d77\u4e2a\u4f60\u559c\u6b22\u7684\u540d\u5b57 mods=Mod\u7ba1\u7406 mods.choose_mod=\u9009\u62e9\u6a21\u7ec4 @@ -257,6 +260,7 @@ launcher.enable_shadow=\u542f\u7528\u7a97\u53e3\u9634\u5f71(\u91cd\u542f\u542f\u launcher.theme=\u4e3b\u9898 launcher.proxy=\u4ee3\u7406 launcher.decorated=\u542f\u7528\u7a97\u53e3\u8fb9\u6846(Linux\u4e0b\u53ef\u89e3\u51b3\u7a0b\u5e8f\u754c\u9762\u5168\u7070\u95ee\u9898) +launcher.modpack=\u6574\u5408\u5305\u4f5c\u8005\u5e2e\u52a9 launcher.title.game=\u6e38\u620f\u8bbe\u7f6e launcher.title.main=\u4e3b\u9875 diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle.properties new file mode 100644 index 000000000..a8133443b --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle.properties @@ -0,0 +1,25 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Next_>=Next > +Next_mnemonic=N +<_Prev=< Prev +Prev_mnemonic=P +Finish=Finish +Finish_mnemonic=F +Cancel=Cancel +Cancel_mnemonic=C +Help=Help +Help_mnemonic=H +Close=Close +Close_mnemonic=C +Summary=Summary +Failed=Failed diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_de.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_de.properties new file mode 100644 index 000000000..7c99c775e --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_de.properties @@ -0,0 +1,25 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Next_>=Weiter > +Next_mnemonic=W +<_Prev=< Zur\u00FCck +Prev_mnemonic=Z +Finish=Fertig +Finish_mnemonic=F +Cancel=Abbrechen +Cancel_mnemonic=A +Help=Hilfe +Help_mnemonic=H +Close=Schlie\u00DFen +Close_mnemonic=S +Summary=Zusammenfassung +Failed=Fehler diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_el_GR.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_el_GR.properties new file mode 100644 index 000000000..c4fc46dce --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_el_GR.properties @@ -0,0 +1,30 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Next_>=\u0395\u03C0\u03CC\u03BC\u03B5\u03BD\u03BF > +Next_mnemonic=\u0395 +<_Prev=< \u03A0\u03C1\u03BF\u03B7\u03B3\u03BF\u03CD\u03BC\u03B5\u03BD\u03BF +Prev_mnemonic=\u03A0 +Finish=\u03A4\u03AD\u03BB\u03BF\u03C2 +Finish_mnemonic=\u03A4 +Cancel=\u0391\u03BA\u03CD\u03C1\u03C9\u03C3\u03B7 +Cancel_mnemonic=\u0391 +Help=\u0392\u03BF\u03AE\u03B8\u03B5\u03B9\u03B1 +Help_mnemonic=\u0392 +Close=\u039A\u03BB\u03B5\u03AF\u03C3\u03B9\u03BC\u03BF +Close_mnemonic=\u039A +Summary=\u03A0\u03B5\u03C1\u03AF\u03BB\u03B7\u03C8\u03B7 +Failed=\u0391\u03C0\u03AD\u03C4\u03C5\u03C7\u03B5 +HELP=\u0392\u039F\u0397\u0398\u0395\u0399\u0391 +FINISH=\u03A4\u0395\u039B\u039F\u03A3 +NEXT=\u0395\u03A0\u039F\u039C\u0395\u039D\u039F +PREV=\u03A0\u03A1\u039F\u0397\u0393\u039F\u03A5\u039C\u0395\u039D\u039F +CANCEL=\u0391\u039A\u03A5\u03A1\u03A9\u03A3\u0397 diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_fr.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_fr.properties new file mode 100644 index 000000000..ac1a04ec2 --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_fr.properties @@ -0,0 +1,25 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Next_>=Suivant > +Next_mnemonic=S +<_Prev=< Pr\u00E9c\u00E9dent +Prev_mnemonic=P +Finish=Terminer +Finish_mnemonic=T +Cancel=Annuler +Cancel_mnemonic=n +Help=Aide +Help_mnemonic=A +Close=Fermer +Close_mnemonic=F +Summary=Sommaire +Failed=\u00C9chec diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_it.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_it.properties new file mode 100644 index 000000000..d4ad9c39d --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_it.properties @@ -0,0 +1,25 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Next_>=Avanti > +Next_mnemonic=v +<_Prev=< Indietro +Prev_mnemonic=I +Finish=Fine +Finish_mnemonic=F +Cancel=Annulla +Cancel_mnemonic=n +Help=Aiuto +Help_mnemonic=A +Close=Chiudi +Close_mnemonic=C +Summary=Riassunto +Failed=Fallimento diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_nl.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_nl.properties new file mode 100644 index 000000000..27b81bd1f --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_nl.properties @@ -0,0 +1,25 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Next_>=Volgende > +Next_mnemonic=g +<_Prev=< Vorige +Prev_mnemonic=r +Finish=Be\u00EBindigen +Finish_mnemonic=B +Cancel=Annuleren +Cancel_mnemonic=A +Help=Help +Help_mnemonic=H +Close=Sluiten +Close_mnemonic=S +Summary=Sammenvatting +Failed=Gefaald diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_pt.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_pt.properties new file mode 100644 index 000000000..6bd67bf44 --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_pt.properties @@ -0,0 +1,27 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Next_>=Pr\u00F3ximo > +Next_mnemonic=P +<_Prev=< Previs\u00E3o +Prev_mnemonic=r +Finish=Fim +Finish_mnemonic=F +Cancel=Cancelar +Cancel_mnemonic=C +Help=Ajudar +Help_mnemonic=A +Close=Fechar +Close_mnemonic=F +Summary=Sum\u00E1rio + +Failed=Falhou + diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_sv_SE.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_sv_SE.properties new file mode 100644 index 000000000..7bcf41839 --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/Bundle_sv_SE.properties @@ -0,0 +1,31 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Next_>=N\u00E4sta > +Next_mnemonic=N +<_Prev=< F\u00F6reg\u00E5ende +Prev_mnemonic=P +Finish=Avsluta +Finish_mnemonic=F +Cancel=Avbryt +Cancel_mnemonic=C +Help=Hj\u00E4lp +Help_mnemonic=H +Close=St\u00E4ng +Close_mnemonic=C +Summary=Sammanfatning +Failed=Misslyckades +HELP=HJ\u00C4LP +FINISH=AVSLUTA +NEXT=N\u00C4STA +PREV=F\u00D6REG\u00C5ENDE +CANCEL=AVBRYT + diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/busy.gif b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/busy.gif new file mode 100644 index 000000000..175581e27 Binary files /dev/null and b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/api/displayer/busy.gif differ diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle.properties new file mode 100644 index 000000000..409aa533e --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle.properties @@ -0,0 +1,18 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Cannot_back_out_past_first_entry=Cannot back out past first entry +elipsis=... +Steps=Steps +Summary=Summary +ACN_InstructionsPanel=Wizard Step List +ACSD_InstructionsPanel=List of the panels in this wizard, which can change \ + based on the choices made on individual panels within it diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_de_DE.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_de_DE.properties new file mode 100644 index 000000000..a3869fac8 --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_de_DE.properties @@ -0,0 +1,17 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +elipsis=... +Steps=Arbeitsschritte +Summary=Zusammenfassung +ACN_InstructionsPanel=Arbeitsschritte des Assistenten +ACSD_InstructionsPanel=Seiten\u00FCbersicht dieses Assistenten; sie kann sich \u00E4ndern \ + je nach dem, welche Entscheidungen Sie treffen. diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_el_GR.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_el_GR.properties new file mode 100644 index 000000000..eac692402 --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_el_GR.properties @@ -0,0 +1,26 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +OpenIDE-Module-Name=Wizards API +OpenIDE-Module-Display-Category=Infrastructure +OpenIDE-Module-Short-Description=Provides the Wizards API +OpenIDE-Module-Long-Description=Provides the Wizards API and a basic implementation of it + +Cannot_back_out_past_first_entry=Cannot back out past first entry + +elipsis=... +elipsis.=... + +Steps=\u0392\u03AE\u03BC\u03B1\u03C4\u03B1 +Summary=\u03A0\u03B5\u03C1\u03AF\u03BB\u03B7\u03C8\u03B7 + +ACN_InstructionsPanel=\u039A\u03B1\u03C4\u03AC\u03BB\u03BF\u03B3\u03BF\u03C2 \u03B2\u03B7\u03BC\u03AC\u03C4\u03C9\u03BD \u03BF\u03B4\u03B7\u03B3\u03BF\u03CD +ACSD_InstructionsPanel=\u039A\u03B1\u03C4\u03AC\u03BB\u03BF\u03B3\u03BF\u03C2 \u03C4\u03C9\u03BD \u03C0\u03AC\u03BD\u03B5\u03BB \u03C3\u03B5 \u03B1\u03C5\u03C4\u03CC\u03BD \u03C4\u03BF\u03BD \u03BF\u03B4\u03B7\u03B3\u03CC, \u03BF \u03BF\u03C0\u03BF\u03AF\u03BF\u03C2 \u03BC\u03C0\u03BF\u03C1\u03B5\u03AF \u03BD\u03B1 \u03B1\u03BB\u03BB\u03AC\u03BE\u03B5\u03B9 \u03B1\u03BD\u03AC\u03BB\u03BF\u03B3\u03B1 \u03BC\u03B5 \u03C4\u03B9\u03C2 \u03B5\u03C0\u03B9\u03BB\u03BF\u03B3\u03AD\u03C2 \u03C3\u03C4\u03BF \u03BA\u03AC\u03B8\u03B5 \u03C0\u03AC\u03BD\u03B5\u03BB. diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_fr.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_fr.properties new file mode 100644 index 000000000..f29c473bc --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_fr.properties @@ -0,0 +1,18 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +Cannot_back_out_past_first_entry=Cannot back out past first entry +elipsis=... +Steps=\u00C9tapes +Summary=Sommaire +ACN_InstructionsPanel=Liste des \u00E9tapes de l'assistant +ACSD_InstructionsPanel=La liste des \u00E9tapes de l'assistant \ + qui peuvent changer selon les choix de l'utilisateur diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_sv_SE.properties b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_sv_SE.properties new file mode 100644 index 000000000..80b33d71c --- /dev/null +++ b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/Bundle_sv_SE.properties @@ -0,0 +1,26 @@ +#The contents of this file are subject to the terms of the Common Development +#and Distribution License (the License). You may not use this file except in +#compliance with the License. +# You can obtain a copy of the License at http://www.netbeans.org/cddl.html +#or http://www.netbeans.org/cddl.txt. +# When distributing Covered Code, include this CDDL Header Notice in each file +#and include the License file at http://www.netbeans.org/cddl.txt. +#If applicable, add the following below the CDDL Header, with the fields +#enclosed by brackets [] replaced by your own identifying information: +#"Portions Copyrighted [year] [name of copyright owner]" + +OpenIDE-Module-Name=Wizards API +OpenIDE-Module-Display-Category=Infrastructure +OpenIDE-Module-Short-Description=Provides the Wizards API +OpenIDE-Module-Long-Description=Provides the Wizards API and a basic implementation of it + +Cannot_back_out_past_first_entry=Cannot back out past first entry + +elipsis=... +elipsis.=... + +Steps=Steg +Summary=Sammanfattning + +ACN_InstructionsPanel=Steg i guiden +ACSD_InstructionsPanel=Lista med steg i guiden, som kan \u00E4ndras beroende p\u00E5 valen som g\u00F6rs i indivuduella paneler diff --git a/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/defaultWizard.png b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/defaultWizard.png new file mode 100644 index 000000000..85cc571db Binary files /dev/null and b/HMCLAPI/src/main/resources/org/jackhuang/hellominecraft/utils/views/wizard/modules/defaultWizard.png differ diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/Main.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/Main.java index 610b2ee5b..e3775279b 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/Main.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/Main.java @@ -23,7 +23,7 @@ import java.io.StringWriter; import java.text.ParseException; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.views.LogWindow; import org.jackhuang.hellominecraft.svrmgr.settings.SettingsManager; import org.jackhuang.hellominecraft.utils.UpdateChecker; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/bukkit/BukkitFormatThread.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/bukkit/BukkitFormatThread.java index a2004f1a9..0eb485d89 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/bukkit/BukkitFormatThread.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/bukkit/BukkitFormatThread.java @@ -20,7 +20,7 @@ package org.jackhuang.hellominecraft.svrmgr.installer.bukkit; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.functions.Consumer; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/cauldron/ForgeFormatThread.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/cauldron/ForgeFormatThread.java index 66b62caf5..3b41f4bc9 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/cauldron/ForgeFormatThread.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/cauldron/ForgeFormatThread.java @@ -23,7 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import org.jackhuang.hellominecraft.utils.functions.Consumer; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/cauldron/ForgeInstaller.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/cauldron/ForgeInstaller.java index caa50ce7e..24d8125c3 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/cauldron/ForgeInstaller.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/installer/cauldron/ForgeInstaller.java @@ -36,7 +36,7 @@ import java.util.jar.Pack200; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.swing.JOptionPane; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.code.DigestUtils; import org.jackhuang.hellominecraft.utils.system.FileUtils; import org.jackhuang.hellominecraft.utils.system.IOUtils; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/Server.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/Server.java index ecf2715f5..6e279da0a 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/Server.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/Server.java @@ -35,7 +35,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.system.IOUtils; import org.jackhuang.hellominecraft.utils.MessageBox; import org.jackhuang.hellominecraft.utils.Pair; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/ServerChecker.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/ServerChecker.java index a36759c76..74fefe23c 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/ServerChecker.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/ServerChecker.java @@ -20,7 +20,7 @@ package org.jackhuang.hellominecraft.svrmgr.server; import java.io.File; import java.io.IOException; import java.util.zip.ZipFile; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; /** * diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/backups/BackupManager.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/backups/BackupManager.java index 45342e6d7..37be64938 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/backups/BackupManager.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/backups/BackupManager.java @@ -22,7 +22,7 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.system.Compressor; import org.jackhuang.hellominecraft.svrmgr.settings.SettingsManager; import org.jackhuang.hellominecraft.svrmgr.utils.Utilities; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/schedules/AutoExecuteSchedule.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/schedules/AutoExecuteSchedule.java index c8e510d42..345f3057c 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/schedules/AutoExecuteSchedule.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/server/schedules/AutoExecuteSchedule.java @@ -19,7 +19,7 @@ package org.jackhuang.hellominecraft.svrmgr.server.schedules; import java.io.IOException; import java.util.TimerTask; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.svrmgr.server.Server; import org.jackhuang.hellominecraft.svrmgr.settings.Schedule; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/PlayerList.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/PlayerList.java index a20dd9671..93726d2e6 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/PlayerList.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/PlayerList.java @@ -22,7 +22,7 @@ import java.io.File; import java.io.IOException; import java.util.HashSet; import java.util.UUID; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.system.FileUtils; import org.jackhuang.hellominecraft.utils.StrUtils; import org.jackhuang.hellominecraft.svrmgr.settings.PlayerList.BasePlayer; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/ServerProperties.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/ServerProperties.java index 65fb38dd9..ad2f3d94b 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/ServerProperties.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/ServerProperties.java @@ -25,7 +25,7 @@ import java.io.InputStream; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Properties; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; /** * diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/SettingsManager.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/SettingsManager.java index 1df771e15..daa18fd8c 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/SettingsManager.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/settings/SettingsManager.java @@ -24,7 +24,7 @@ package org.jackhuang.hellominecraft.svrmgr.settings; import com.google.gson.Gson; import java.io.File; import java.io.IOException; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.system.FileUtils; import org.jackhuang.hellominecraft.utils.system.IOUtils; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/threads/MonitorThread.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/threads/MonitorThread.java index 7e5f45403..876cda7f7 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/threads/MonitorThread.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/threads/MonitorThread.java @@ -23,7 +23,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; /** * diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/utils/IPGet.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/utils/IPGet.java index e1f52a924..626458cb3 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/utils/IPGet.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/utils/IPGet.java @@ -19,7 +19,7 @@ package org.jackhuang.hellominecraft.svrmgr.utils; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.NetUtils; import org.jackhuang.hellominecraft.utils.functions.Consumer; import org.jsoup.Jsoup; diff --git a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/views/MainWindow.java b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/views/MainWindow.java index 0e48f1c14..c959e4d43 100755 --- a/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/views/MainWindow.java +++ b/HMCSM/src/main/java/org/jackhuang/hellominecraft/svrmgr/views/MainWindow.java @@ -46,7 +46,7 @@ import javax.swing.JPopupMenu; import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.table.DefaultTableModel; import org.jackhuang.hellominecraft.utils.C; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.tasks.Task; import org.jackhuang.hellominecraft.utils.tasks.TaskWindow; import org.jackhuang.hellominecraft.utils.tasks.download.FileDownloadTask; diff --git a/MetroLookAndFeel/src/main/java/org/jackhuang/hellominecraft/lookandfeel/HelloMinecraftLookAndFeel.java b/MetroLookAndFeel/src/main/java/org/jackhuang/hellominecraft/lookandfeel/HelloMinecraftLookAndFeel.java index 63c2089ad..7c0b9e33c 100755 --- a/MetroLookAndFeel/src/main/java/org/jackhuang/hellominecraft/lookandfeel/HelloMinecraftLookAndFeel.java +++ b/MetroLookAndFeel/src/main/java/org/jackhuang/hellominecraft/lookandfeel/HelloMinecraftLookAndFeel.java @@ -23,7 +23,7 @@ import java.text.ParseException; import java.util.Map; import javax.swing.UIDefaults; import javax.swing.plaf.synth.SynthLookAndFeel; -import org.jackhuang.hellominecraft.utils.HMCLog; +import org.jackhuang.hellominecraft.utils.logging.HMCLog; import org.jackhuang.hellominecraft.utils.NetUtils; /**