From 0cc95225dddd9224d800f34cc4d3146dc4549876 Mon Sep 17 00:00:00 2001 From: sk89q Date: Thu, 23 Jul 2015 23:46:18 -0700 Subject: [PATCH] Add new creator tools. --- .../launcher/buildtools/BuildTools.java | 462 ----------- .../buildtools/ProjectDirectoryChooser.java | 44 - .../buildtools/ProjectDirectoryDialog.java | 78 -- .../launcher/buildtools/ToolArguments.java | 23 - .../launcher/buildtools/ToolsFrame.java | 104 --- .../skcraft/launcher/buildtools/header.png | Bin 3437 -> 0 bytes {build-tools => creator-tools}/build.gradle | 14 +- .../com/skcraft/launcher/creator/Creator.java | 84 ++ .../controller/GenerateListingController.java | 218 +++++ .../controller/ManifestEntryController.java | 55 ++ .../controller/PackManagerController.java | 752 ++++++++++++++++++ .../creator/controller/WelcomeController.java | 204 +++++ .../controller/task/DirectoryDeleter.java | 21 +- .../task/ManifestInfoEnumerator.java | 66 ++ .../creator/controller/task/PackBuilder.java | 15 +- .../creator/controller/task/PackLoader.java | 57 ++ .../controller/task}/ProblemChecker.java | 31 +- .../creator/controller/task/ServerDeploy.java | 10 +- .../creator/controller/task/TestLauncher.java | 81 ++ .../launcher/creator/dialog/AboutDialog.java | 77 ++ .../launcher/creator/dialog}/BuildDialog.java | 33 +- .../creator/dialog}/BuilderConfigDialog.java | 117 ++- .../creator/dialog}/DeployServerDialog.java | 28 +- .../creator/dialog}/FeaturePatternDialog.java | 61 +- .../creator/dialog}/FeaturePatternTable.java | 16 +- .../creator/dialog/GenerateListingDialog.java | 77 ++ .../creator/dialog/ManifestEntryDialog.java | 64 ++ .../creator/dialog/PackManagerFrame.java | 175 ++++ .../creator/dialog}/ProblemTable.java | 14 +- .../creator/dialog}/ProblemViewer.java | 33 +- .../creator/dialog/WelcomeDialog.java | 70 ++ .../creator/model/creator/CreatorConfig.java | 33 + .../creator/model/creator/ManifestEntry.java | 27 + .../launcher/creator/model/creator/Pack.java | 67 ++ .../creator/model/creator}/Problem.java | 2 +- .../creator/model/creator/RecentEntry.java | 27 + .../creator/model/creator/Workspace.java | 78 ++ .../swing}/FeaturePatternTableModel.java | 4 +- .../creator/model/swing/ListingType.java | 96 +++ .../model/swing/ListingTypeComboBoxModel.java | 35 + .../model/swing/ManifestEntryTableModel.java | 122 +++ .../creator/model/swing/PackTableModel.java | 101 +++ .../model/swing}/ProblemTableModel.java | 8 +- .../creator/model/swing/RecentListModel.java | 35 + .../swing}/RecommendationComboBoxModel.java | 4 +- .../creator/server}/LatestHandler.java | 2 +- .../launcher/creator/server}/NewsHandler.java | 2 +- .../creator/server}/PackagesHandler.java | 2 +- .../launcher/creator/server/TestServer.java | 32 + .../creator/server/TestServerBuilder.java | 12 +- .../creator/swing/BorderCellRenderer.java | 28 + .../creator/swing/PackDirectoryFilter.java | 26 + .../swing/WorkspaceDirectoryFilter.java | 24 + .../skcraft/launcher/creator/about_header.png | Bin 0 -> 39221 bytes .../com/skcraft/launcher/creator}/build.png | Bin .../com/skcraft/launcher/creator}/check.png | Bin .../com/skcraft/launcher/creator}/clean.png | Bin .../launcher/creator/creator.properties | 7 + .../com/skcraft/launcher/creator}/edit.png | Bin .../com/skcraft/launcher/creator}/help.png | Bin .../com/skcraft/launcher/creator}/icon.png | Bin .../com/skcraft/launcher/creator/import.png | Bin 0 -> 540 bytes .../com/skcraft/launcher/creator}/log.png | Bin .../com/skcraft/launcher/creator/new.png | Bin 0 -> 685 bytes .../skcraft/launcher/creator}/open_folder.png | Bin .../com/skcraft/launcher/creator}/options.png | Bin .../com/skcraft/launcher/creator}/server.png | Bin .../com/skcraft/launcher/creator}/test.png | Bin .../launcher/creator}/warning_icon.png | Bin .../skcraft/launcher/creator/welcome_logo.png | Bin 0 -> 14735 bytes .../launcher/builder/BuilderUtils.java | 13 +- .../com/skcraft/concurrency/Callback.java | 13 + .../com/skcraft/concurrency/Deferred.java | 142 ++++ .../com/skcraft/concurrency/DeferredImpl.java | 140 ++++ .../com/skcraft/concurrency/Deferreds.java | 45 ++ .../skcraft/concurrency/SettableProgress.java | 46 ++ .../com/skcraft/launcher/LauncherUtils.java | 16 +- .../launcher/dialog/ProgressDialog.java | 7 +- .../launcher/model/modpack/ManifestInfo.java | 13 +- .../launcher/model/modpack/PackageList.java | 2 + .../launcher/persistence/Persistence.java | 26 +- .../skcraft/launcher/swing/CheckboxTable.java | 12 +- .../skcraft/launcher/swing/DefaultTable.java | 25 + .../launcher/swing/DirectoryField.java | 2 + .../launcher/swing/EventQueueExecutor.java | 24 + .../skcraft/launcher/swing/InstanceTable.java | 10 +- .../skcraft/launcher/swing/SwingHelper.java | 32 + .../launcher/swing/TableColumnAdjuster.java | 326 ++++++++ .../com/skcraft/launcher/util/MorePaths.java | 45 ++ .../skcraft/launcher/util/SwingExecutor.java | 22 +- settings.gradle | 2 +- 91 files changed, 3835 insertions(+), 986 deletions(-) delete mode 100644 build-tools/src/main/java/com/skcraft/launcher/buildtools/BuildTools.java delete mode 100644 build-tools/src/main/java/com/skcraft/launcher/buildtools/ProjectDirectoryChooser.java delete mode 100644 build-tools/src/main/java/com/skcraft/launcher/buildtools/ProjectDirectoryDialog.java delete mode 100644 build-tools/src/main/java/com/skcraft/launcher/buildtools/ToolArguments.java delete mode 100644 build-tools/src/main/java/com/skcraft/launcher/buildtools/ToolsFrame.java delete mode 100644 build-tools/src/main/resources/com/skcraft/launcher/buildtools/header.png rename {build-tools => creator-tools}/build.gradle (51%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/Creator.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/controller/GenerateListingController.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/controller/ManifestEntryController.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/controller/PackManagerController.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/controller/WelcomeController.java rename build-tools/src/main/java/com/skcraft/launcher/buildtools/DirectoryRemover.java => creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/DirectoryDeleter.java (68%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ManifestInfoEnumerator.java rename build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ModpackBuilder.java => creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/PackBuilder.java (80%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/PackLoader.java rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/compile => creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task}/ProblemChecker.java (78%) rename build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ServerDeployer.java => creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ServerDeploy.java (83%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/TestLauncher.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/AboutDialog.java rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/compile => creator-tools/src/main/java/com/skcraft/launcher/creator/dialog}/BuildDialog.java (77%) rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/project => creator-tools/src/main/java/com/skcraft/launcher/creator/dialog}/BuilderConfigDialog.java (63%) rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/compile => creator-tools/src/main/java/com/skcraft/launcher/creator/dialog}/DeployServerDialog.java (78%) rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/project => creator-tools/src/main/java/com/skcraft/launcher/creator/dialog}/FeaturePatternDialog.java (74%) rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/project => creator-tools/src/main/java/com/skcraft/launcher/creator/dialog}/FeaturePatternTable.java (52%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/GenerateListingDialog.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ManifestEntryDialog.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/PackManagerFrame.java rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/compile => creator-tools/src/main/java/com/skcraft/launcher/creator/dialog}/ProblemTable.java (55%) rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/compile => creator-tools/src/main/java/com/skcraft/launcher/creator/dialog}/ProblemViewer.java (71%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/WelcomeDialog.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/CreatorConfig.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/ManifestEntry.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Pack.java rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/compile => creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator}/Problem.java (90%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/RecentEntry.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Workspace.java rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/project => creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing}/FeaturePatternTableModel.java (95%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ListingType.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ListingTypeComboBoxModel.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ManifestEntryTableModel.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/PackTableModel.java rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/compile => creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing}/ProblemTableModel.java (85%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/RecentListModel.java rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/project => creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing}/RecommendationComboBoxModel.java (84%) rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/http => creator-tools/src/main/java/com/skcraft/launcher/creator/server}/LatestHandler.java (96%) rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/http => creator-tools/src/main/java/com/skcraft/launcher/creator/server}/NewsHandler.java (96%) rename {build-tools/src/main/java/com/skcraft/launcher/buildtools/http => creator-tools/src/main/java/com/skcraft/launcher/creator/server}/PackagesHandler.java (98%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/server/TestServer.java rename build-tools/src/main/java/com/skcraft/launcher/buildtools/http/LocalHttpServerBuilder.java => creator-tools/src/main/java/com/skcraft/launcher/creator/server/TestServerBuilder.java (89%) create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/swing/BorderCellRenderer.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/swing/PackDirectoryFilter.java create mode 100644 creator-tools/src/main/java/com/skcraft/launcher/creator/swing/WorkspaceDirectoryFilter.java create mode 100644 creator-tools/src/main/resources/com/skcraft/launcher/creator/about_header.png rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/build.png (100%) rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/check.png (100%) rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/clean.png (100%) create mode 100644 creator-tools/src/main/resources/com/skcraft/launcher/creator/creator.properties rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/edit.png (100%) rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/help.png (100%) rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/icon.png (100%) create mode 100644 creator-tools/src/main/resources/com/skcraft/launcher/creator/import.png rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/log.png (100%) create mode 100644 creator-tools/src/main/resources/com/skcraft/launcher/creator/new.png rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/open_folder.png (100%) rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/options.png (100%) rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/server.png (100%) rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools => creator-tools/src/main/resources/com/skcraft/launcher/creator}/test.png (100%) rename {build-tools/src/main/resources/com/skcraft/launcher/buildtools/compile => creator-tools/src/main/resources/com/skcraft/launcher/creator}/warning_icon.png (100%) create mode 100644 creator-tools/src/main/resources/com/skcraft/launcher/creator/welcome_logo.png create mode 100644 launcher/src/main/java/com/skcraft/concurrency/Callback.java create mode 100644 launcher/src/main/java/com/skcraft/concurrency/Deferred.java create mode 100644 launcher/src/main/java/com/skcraft/concurrency/DeferredImpl.java create mode 100644 launcher/src/main/java/com/skcraft/concurrency/Deferreds.java create mode 100644 launcher/src/main/java/com/skcraft/concurrency/SettableProgress.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/swing/DefaultTable.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/swing/EventQueueExecutor.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/swing/TableColumnAdjuster.java create mode 100644 launcher/src/main/java/com/skcraft/launcher/util/MorePaths.java diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/BuildTools.java b/build-tools/src/main/java/com/skcraft/launcher/buildtools/BuildTools.java deleted file mode 100644 index b67307c..0000000 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/BuildTools.java +++ /dev/null @@ -1,462 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.buildtools; - -import com.beust.jcommander.JCommander; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; -import com.fasterxml.jackson.core.util.DefaultPrettyPrinter.Lf2SpacesIndenter; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.skcraft.concurrency.ObservableFuture; -import com.skcraft.launcher.Instance; -import com.skcraft.launcher.InstanceList; -import com.skcraft.launcher.Launcher; -import com.skcraft.launcher.auth.OfflineSession; -import com.skcraft.launcher.auth.Session; -import com.skcraft.launcher.builder.BuilderConfig; -import com.skcraft.launcher.builder.BuilderOptions; -import com.skcraft.launcher.builder.FnPatternList; -import com.skcraft.launcher.buildtools.compile.*; -import com.skcraft.launcher.buildtools.compile.BuildDialog.BuildOptions; -import com.skcraft.launcher.buildtools.project.BuilderConfigDialog; -import com.skcraft.launcher.buildtools.http.LocalHttpServerBuilder; -import com.skcraft.launcher.buildtools.compile.DeployServerDialog.DeployOptions; -import com.skcraft.launcher.dialog.ConfigurationDialog; -import com.skcraft.launcher.dialog.ConsoleFrame; -import com.skcraft.launcher.dialog.ProgressDialog; -import com.skcraft.launcher.launch.LaunchOptions; -import com.skcraft.launcher.launch.LaunchOptions.UpdatePolicy; -import com.skcraft.launcher.model.modpack.LaunchModifier; -import com.skcraft.launcher.persistence.Persistence; -import com.skcraft.launcher.swing.SwingHelper; -import lombok.Getter; -import lombok.extern.java.Log; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.List; -import java.util.logging.Level; -import java.util.regex.Pattern; - -@Log -public class BuildTools { - - private static final DateFormat VERSION_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); - private static final Pattern FILENAME_SANITIZE = Pattern.compile("[^a-z0-9_\\-\\.]+"); - private static final DefaultPrettyPrinter lf2ListPrettyPrinter; - - private final Launcher launcher; - @Getter - private int port; - @Getter - private final File inputDir; - @Getter - private final File srcDir; - @Getter - private final File wwwDir; - @Getter - private final File distDir; - - static { - lf2ListPrettyPrinter = new DefaultPrettyPrinter(); - lf2ListPrettyPrinter.indentArraysWith(Lf2SpacesIndenter.instance); - } - - @SuppressWarnings("ResultOfMethodCallIgnored") - public BuildTools(File baseDir, int port) throws IOException { - File launcherDir = new File(baseDir, "staging/launcher"); - inputDir = baseDir; - srcDir = new File(baseDir, BuilderOptions.DEFAULT_SRC_DIRNAME); - wwwDir = new File(baseDir, "staging/www"); - distDir = new File(baseDir, "upload"); - - this.port = port; - - srcDir.mkdirs(); - new File(baseDir, BuilderOptions.DEFAULT_LOADERS_DIRNAME).mkdir(); - launcherDir.mkdirs(); - wwwDir.mkdirs(); - - launcher = new Launcher(launcherDir); - setPort(port); - } - - private void setPort(int port) { - this.port = port; - launcher.getProperties().setProperty("newsUrl", "http://localhost:" + port + "/news.html"); - launcher.getProperties().setProperty("packageListUrl", "http://localhost:" + port + "/packages.json"); - launcher.getProperties().setProperty("selfUpdateUrl", "http://localhost:" + port + "/latest.json"); - } - - public File getConfigFile() { - return new File(inputDir, BuilderOptions.DEFAULT_CONFIG_FILENAME); - } - - public String generateManifestName() { - File file = getConfigFile(); - if (file.exists()) { - BuilderConfig config = Persistence.read(file, BuilderConfig.class, true); - if (config != null) { - String name = Strings.nullToEmpty(config.getName()); - name = name.toLowerCase(); - name = FILENAME_SANITIZE.matcher(name).replaceAll("-"); - name = name.trim(); - if (!name.isEmpty()) { - return name + ".json"; - } - } - } - - return "my_modpack.json"; - } - - public String getCurrentModpackName() { - File file = getConfigFile(); - if (file.exists()) { - BuilderConfig config = Persistence.read(file, BuilderConfig.class, true); - if (config != null) { - return config.getName(); - } - } - - return null; - } - - public Instance findCurrentInstance(List instances) { - String expected = getCurrentModpackName(); - - for (Instance instance : instances) { - if (instance.getName().equals(expected)) { - return instance; - } - } - - return null; - } - - private void addDefaultConfig(BuilderConfig config) { - config.setName("My Modpack"); - config.setTitle("My Modpack"); - config.setGameVersion("1.8"); - - LaunchModifier launchModifier = new LaunchModifier(); - launchModifier.setFlags(ImmutableList.of("-Dfml.ignoreInvalidMinecraftCertificates=true")); - config.setLaunchModifier(launchModifier); - - FnPatternList userFiles = new FnPatternList(); - userFiles.setInclude(Lists.newArrayList("options.txt", "optionsshaders.txt", "mods/VoxelMods/*")); - userFiles.setExclude(Lists.newArrayList()); - config.setUserFiles(userFiles); - } - - public Server startHttpServer() throws Exception { - LocalHttpServerBuilder builder = new LocalHttpServerBuilder(); - builder.setBaseDir(wwwDir); - builder.setPort(port); - - Server server = builder.build(); - server.start(); - setPort(((ServerConnector) server.getConnectors()[0]).getLocalPort()); - return server; - } - - private void showMainWindow() { - final ToolsFrame frame = new ToolsFrame(); - - frame.getEditConfigButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent event) { - File file = getConfigFile(); - boolean existed = file.exists(); - - BuilderConfig config = Persistence.read(file, BuilderConfig.class); - if (!existed) { - addDefaultConfig(config); - } - - if (BuilderConfigDialog.showEditor(frame, config)) { - try { - Persistence.write(file, config, lf2ListPrettyPrinter); - } catch (IOException e) { - SwingHelper.showErrorDialog(frame, "Couldn't write modpack.json to disk due to an error", "Write Error", e); - } - } - } - }); - - frame.getOpenFolderButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - SwingHelper.browseDir(getInputDir(), frame); - } - }); - - frame.getCheckProblemsButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ProblemChecker runnable = new ProblemChecker(BuildTools.this); - ObservableFuture> future = new ObservableFuture>(launcher.getExecutor().submit(runnable), runnable); - ProgressDialog.showProgress(frame, future, "Checking for problems...", "Checking for problems..."); - - Futures.addCallback(future, new FutureCallback>() { - @Override - public void onSuccess(List problems) { - if (problems.isEmpty()) { - SwingHelper.showMessageDialog(frame, "No potential problems found!", "Success", null, JOptionPane.INFORMATION_MESSAGE); - } else { - ProblemViewer viewer = new ProblemViewer(frame, problems); - viewer.setVisible(true); - } - } - - @Override - public void onFailure(Throwable t) { - } - }); - - SwingHelper.addErrorDialogCallback(frame, future); - } - }); - - frame.getBuildButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final BuildOptions options = BuildDialog.showBuildDialog(frame, generateVersionFromDate(), generateManifestName(), distDir); - if (options != null) { - ConsoleFrame.showMessages(); - - options.getDestDir().mkdirs(); - ModpackBuilder runnable = new ModpackBuilder(inputDir, options.getDestDir(), options.getVersion(), options.getManifestFilename(), options.isClean()); - ObservableFuture future = new ObservableFuture(launcher.getExecutor().submit(runnable), runnable); - ProgressDialog.showProgress(frame, future, "Building modpack...", "Building modpack for release..."); - - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(ModpackBuilder result) { - SwingHelper.browseDir(options.getDestDir(), frame); - } - - @Override - public void onFailure(Throwable t) { - } - }); - - SwingHelper.addErrorDialogCallback(frame, future); - } - } - }); - - frame.getDeployServerButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - final DeployOptions options = DeployServerDialog.showDeployDialog(frame); - if (options != null) { - ConsoleFrame.showMessages(); - - distDir.mkdirs(); - ServerDeployer runnable = new ServerDeployer(srcDir, options); - ObservableFuture future = new ObservableFuture(launcher.getExecutor().submit(runnable), runnable); - ProgressDialog.showProgress(frame, future, "Deploying files...", "Deploying server files..."); - - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(ServerDeployer result) { - SwingHelper.showMessageDialog(frame, "Server deployment complete!", "Success", null, JOptionPane.INFORMATION_MESSAGE); - } - - @Override - public void onFailure(Throwable t) { - } - }); - - SwingHelper.addErrorDialogCallback(frame, future); - } - } - }); - - frame.getTestButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ConsoleFrame.showMessages(); - - ModpackBuilder runnable = new ModpackBuilder(inputDir, wwwDir, generateVersionFromDate(), "staging.json", false); - ObservableFuture future = new ObservableFuture(launcher.getExecutor().submit(runnable), runnable); - ProgressDialog.showProgress(frame, future, "Preparing files...", "Preparing files for launch..."); - SwingHelper.addErrorDialogCallback(frame, future); - - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(ModpackBuilder result) { - launchInstance(frame); - ConsoleFrame.hideMessages(); - } - - @Override - public void onFailure(Throwable t) { - } - }); - } - }); - - frame.getOptionsButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ConfigurationDialog configDialog = new ConfigurationDialog(frame, launcher); - configDialog.setVisible(true); - } - }); - - frame.getClearInstanceButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - DirectoryRemover remover = new DirectoryRemover(launcher.getInstancesDir()); - ObservableFuture future = new ObservableFuture(launcher.getExecutor().submit(remover), remover); - ProgressDialog.showProgress(frame, future, "Removing files...", "Removing files..."); - SwingHelper.addErrorDialogCallback(frame, future); - } - }); - - frame.getClearWebRootButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - DirectoryRemover remover = new DirectoryRemover(wwwDir); - ObservableFuture future = new ObservableFuture(launcher.getExecutor().submit(remover), remover); - ProgressDialog.showProgress(frame, future, "Removing files...", "Removing files..."); - SwingHelper.addErrorDialogCallback(frame, future); - } - }); - - frame.getOpenConsoleButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - ConsoleFrame.showMessages(); - } - }); - - frame.getDocsButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - SwingHelper.openURL("https://github.com/SKCraft/Launcher/wiki", frame); - } - }); - - frame.getQuitButton().addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - frame.dispose(); - System.exit(0); - } - }); - - frame.setVisible(true); - } - - private void launchInstance(final Window window) { - final InstanceList instanceList = launcher.getInstances(); - InstanceList.Enumerator loader = instanceList.createEnumerator(); - ObservableFuture future = new ObservableFuture(launcher.getExecutor().submit(loader), loader); - ProgressDialog.showProgress(window, future, "Loading modpacks...", "Loading modpacks..."); - SwingHelper.addErrorDialogCallback(window, future); - - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(InstanceList result) { - Session session = new OfflineSession("Player"); - - Instance instance = findCurrentInstance(instanceList.getInstances()); - - if (instance != null) { - LaunchOptions options = new LaunchOptions.Builder() - .setInstance(instance) - .setUpdatePolicy(UpdatePolicy.ALWAYS_UPDATE) - .setWindow(window) - .setSession(session) - .build(); - - launcher.getLaunchSupervisor().launch(options); - } else { - SwingHelper.showErrorDialog(window, - "After generating the necessary files, it appears the modpack can't be found in the " + - "launcher. Did you change modpack.json while the launcher was launching?", "Launch Error"); - } - } - - @Override - public void onFailure(Throwable t) { - } - }); - } - - public static void main(String[] args) throws Exception { - Launcher.setupLogger(); - System.setProperty("skcraftLauncher.killWithoutConfirm", "true"); - - ToolArguments options = new ToolArguments(); - new JCommander(options, args); - - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - UIManager.getDefaults().put("SplitPane.border", BorderFactory.createEmptyBorder()); - try { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception ignored) { - } - } - }); - - File inputDir = new ProjectDirectoryChooser(options.getDir()).choose(); - - if (inputDir == null) { - System.exit(100); - return; - } - - final BuildTools main = new BuildTools(inputDir, options.getPort()); - - try { - main.startHttpServer(); - } catch (Throwable t) { - log.log(Level.WARNING, "Web server start failure", t); - SwingHelper.showErrorDialog(null, "Couldn't start the local web server on a free TCP port! " + - "The web server is required to temporarily host the modpack files for the launcher.", "Build Tools Error", t); - System.exit(1); - return; - } - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - try { - main.showMainWindow(); - } catch (Throwable t) { - log.log(Level.WARNING, "Load failure", t); - SwingHelper.showErrorDialog(null, "Failed to launch build tools!", "Build Tools Error", t); - } - } - }); - } - - public static String generateVersionFromDate() { - Date today = Calendar.getInstance().getTime(); - return VERSION_DATE_FORMAT.format(today); - } - -} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/ProjectDirectoryChooser.java b/build-tools/src/main/java/com/skcraft/launcher/buildtools/ProjectDirectoryChooser.java deleted file mode 100644 index f5c9a90..0000000 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/ProjectDirectoryChooser.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.buildtools; - -import com.skcraft.launcher.builder.BuilderOptions; -import lombok.Getter; - -import javax.swing.*; -import java.io.File; -import java.lang.reflect.InvocationTargetException; - -class ProjectDirectoryChooser { - - @Getter - private File inputDir; - - public ProjectDirectoryChooser(File inputDir) { - this.inputDir = inputDir; - } - - public File choose() throws InvocationTargetException, InterruptedException { - final File currentDir = new File("."); - - if (inputDir == null) { - if (new File(currentDir, BuilderOptions.DEFAULT_CONFIG_FILENAME).exists()) { - inputDir = currentDir; - } else { - SwingUtilities.invokeAndWait(new Runnable() { - @Override - public void run() { - inputDir = ProjectDirectoryDialog.showDirectoryDialog(null, currentDir); - } - }); - } - } - - return inputDir; - } - -} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/ProjectDirectoryDialog.java b/build-tools/src/main/java/com/skcraft/launcher/buildtools/ProjectDirectoryDialog.java deleted file mode 100644 index 87e2493..0000000 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/ProjectDirectoryDialog.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.buildtools; - -import com.skcraft.launcher.swing.DirectoryField; -import com.skcraft.launcher.swing.SwingHelper; -import net.miginfocom.swing.MigLayout; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; - -public class ProjectDirectoryDialog extends JDialog { - - private final DirectoryField directoryField = new DirectoryField(); - private File projectDir; - - public ProjectDirectoryDialog(Window parent) { - super(parent, "Select Modpack Directory", ModalityType.DOCUMENT_MODAL); - - setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - initComponents(); - setResizable(false); - pack(); - setLocationRelativeTo(parent); - } - - private void initComponents() { - JPanel container = new JPanel(); - container.setLayout(new MigLayout("insets dialog")); - - container.add(new JLabel("Please select the project directory."), "wrap"); - container.add(directoryField, "span"); - - JButton openButton = new JButton("Open"); - JButton cancelButton = new JButton("Cancel"); - - container.add(openButton, "tag ok, span, split 2, sizegroup bttn, gaptop unrel"); - container.add(cancelButton, "tag cancel, sizegroup bttn"); - - add(container, BorderLayout.CENTER); - - openButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - String path = directoryField.getPath(); - if (path.isEmpty()) { - SwingHelper.showErrorDialog(ProjectDirectoryDialog.this, "Please select a directory.", "No Directory"); - return; - } - - projectDir = new File(path); - dispose(); - } - }); - - cancelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); - } - - public static File showDirectoryDialog(Window parent, File initialDir) { - ProjectDirectoryDialog dialog = new ProjectDirectoryDialog(parent); - dialog.directoryField.setPath(initialDir.getAbsolutePath()); - dialog.setVisible(true); - return dialog.projectDir; - } - -} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/ToolArguments.java b/build-tools/src/main/java/com/skcraft/launcher/buildtools/ToolArguments.java deleted file mode 100644 index 29adc35..0000000 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/ToolArguments.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.buildtools; - -import com.beust.jcommander.Parameter; -import lombok.Data; - -import java.io.File; - -@Data -class ToolArguments { - - @Parameter(names = "--dir") - private File dir; - - @Parameter(names = "--port") - private int port = 0; - -} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/ToolsFrame.java b/build-tools/src/main/java/com/skcraft/launcher/buildtools/ToolsFrame.java deleted file mode 100644 index c7ffd25..0000000 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/ToolsFrame.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * SK's Minecraft Launcher - * Copyright (C) 2010-2014 Albert Pham and contributors - * Please see LICENSE.txt for license information. - */ - -package com.skcraft.launcher.buildtools; - -import com.skcraft.launcher.swing.SwingHelper; -import lombok.Data; -import lombok.extern.java.Log; -import net.miginfocom.swing.MigLayout; - -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; - -@Log -@Data -public class ToolsFrame extends JFrame { - - private final JButton editConfigButton = new JButton("Edit modpack.json", SwingHelper.readImageIcon(ToolsFrame.class, "edit.png")); - private final JButton openFolderButton = new JButton("Open Modpack Files", SwingHelper.readImageIcon(ToolsFrame.class, "open_folder.png")); - private final JButton checkProblemsButton = new JButton("Check for Potential Problems", SwingHelper.readImageIcon(ToolsFrame.class, "check.png")); - private final JButton testButton = new JButton("Run Modpack", SwingHelper.readImageIcon(ToolsFrame.class, "test.png")); - private final JButton buildButton = new JButton("Build Client Modpack", SwingHelper.readImageIcon(ToolsFrame.class, "build.png")); - private final JButton deployServerButton = new JButton("Copy Server Mods to Folder", SwingHelper.readImageIcon(ToolsFrame.class, "server.png")); - private final JButton optionsButton = new JButton("Test Launcher Options", SwingHelper.readImageIcon(ToolsFrame.class, "options.png")); - private final JButton clearInstanceButton = new JButton("Delete Instance from Test Launcher", SwingHelper.readImageIcon(ToolsFrame.class, "clean.png")); - private final JButton clearWebRootButton = new JButton("Delete Generated Modpack Files", SwingHelper.readImageIcon(ToolsFrame.class, "clean.png")); - private final JButton openConsoleButton = new JButton("Console"); - private final JButton docsButton = new JButton("Help"); - private final JButton quitButton = new JButton("Quit"); - - public ToolsFrame() { - super("Modpack Build Tools"); - - setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - initComponents(); - pack(); - setLocationRelativeTo(null); - - SwingHelper.setIconImage(this, ToolsFrame.class, "icon.png"); - } - - private void initComponents() { - JPanel container = new JPanel(); - container.setLayout(new MigLayout("fill, insets dialog, wrap 1")); - - BufferedImage header = SwingHelper.readIconImage(ToolsFrame.class, "header.png"); - if (header != null) { - add(new JLabel(new ImageIcon(header)), BorderLayout.NORTH); - } - - JTabbedPane tabbedPane = new JTabbedPane(); - tabbedPane.addTab("Create", null, createProjectPanel()); - tabbedPane.addTab("Test", null, createTestPanel()); - tabbedPane.addTab("Release", null, createReleasePanel()); - container.add(tabbedPane, "grow, gapbottom 20"); - - container.add(docsButton, "span, split 3, sizegroup bttn"); - container.add(openConsoleButton, "sizegroup bttn"); - container.add(quitButton, "tag ok, sizegroup bttn"); - - add(container, BorderLayout.CENTER); - } - - private JPanel createProjectPanel() { - JPanel container = new JPanel(); - SwingHelper.removeOpaqueness(container); - container.setLayout(new MigLayout("fillx, insets dialog, wrap 1", "", "")); - - container.add(editConfigButton, "grow, tag ok"); - container.add(openFolderButton, "grow, tag ok"); - container.add(checkProblemsButton, "grow, tag ok"); - - return container; - } - - private JPanel createTestPanel() { - JPanel container = new JPanel(); - SwingHelper.removeOpaqueness(container); - container.setLayout(new MigLayout("fillx, insets dialog, wrap 1", "", "")); - - container.add(testButton, "grow, tag ok"); - container.add(optionsButton, "grow, tag ok"); - container.add(clearInstanceButton, "grow, tag ok"); - container.add(clearWebRootButton, "grow, tag ok"); - - return container; - } - - private JPanel createReleasePanel() { - JPanel container = new JPanel(); - SwingHelper.removeOpaqueness(container); - container.setLayout(new MigLayout("fillx, insets dialog, wrap 1", "", "")); - - container.add(buildButton, "grow, tag ok"); - container.add(deployServerButton, "grow, tag ok"); - - return container; - } - -} diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/header.png b/build-tools/src/main/resources/com/skcraft/launcher/buildtools/header.png deleted file mode 100644 index f58b1a0fb0f44f3ab92eb58b1c32ac02c4773970..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3437 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 and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator; + +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.creator.controller.WelcomeController; +import com.skcraft.launcher.creator.dialog.WelcomeDialog; +import com.skcraft.launcher.creator.model.creator.CreatorConfig; +import com.skcraft.launcher.creator.model.creator.RecentEntry; +import com.skcraft.launcher.creator.model.creator.Workspace; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.swing.SwingHelper; +import lombok.Getter; + +import javax.swing.*; +import javax.swing.filechooser.FileSystemView; +import java.io.File; +import java.util.Iterator; +import java.util.List; + +public class Creator { + + @Getter private final File dataDir; + @Getter private final CreatorConfig config; + + public Creator() { + this.dataDir = getAppDataDir(); + this.config = Persistence.load(new File(dataDir, "config.json"), CreatorConfig.class); + + // Remove deleted workspaces + List recentEntries = config.getRecentEntries(); + Iterator it = recentEntries.iterator(); + while (it.hasNext()) { + RecentEntry workspace = it.next(); + if (!Workspace.getWorkspaceFile(workspace.getPath()).exists()) { + it.remove(); + } + } + } + + public void showWelcome() { + WelcomeDialog dialog = new WelcomeDialog(); + WelcomeController controller = new WelcomeController(dialog, this); + controller.show(); + } + + private static File getFileChooseDefaultDir() { + JFileChooser chooser = new JFileChooser(); + FileSystemView fsv = chooser.getFileSystemView(); + return fsv.getDefaultDirectory(); + } + + private static File getAppDataDir() { + String osName = System.getProperty("os.name").toLowerCase(); + if (osName.contains("win")) { + return new File(getFileChooseDefaultDir(), "SKCraft Modpack Creator"); + } else { + return new File(System.getProperty("user.home"), ".skcraftcreator"); + } + } + + public static void main(String[] args) throws Exception { + Launcher.setupLogger(); + System.setProperty("skcraftLauncher.killWithoutConfirm", "true"); + + final Creator creator = new Creator(); + + SwingUtilities.invokeAndWait(() -> { + UIManager.getDefaults().put("SplitPane.border", BorderFactory.createEmptyBorder()); + SwingHelper.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + + try { + creator.showWelcome(); + } catch (Exception e) { + SwingHelper.showErrorDialog(null, "Failed to start the modpack creator program.", "Start Error", e); + } + }); + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/GenerateListingController.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/GenerateListingController.java new file mode 100644 index 0000000..688cad8 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/GenerateListingController.java @@ -0,0 +1,218 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.controller; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import com.google.common.io.Files; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.skcraft.concurrency.Deferred; +import com.skcraft.concurrency.Deferreds; +import com.skcraft.concurrency.SettableProgress; +import com.skcraft.launcher.creator.dialog.GenerateListingDialog; +import com.skcraft.launcher.creator.dialog.ManifestEntryDialog; +import com.skcraft.launcher.creator.model.creator.ManifestEntry; +import com.skcraft.launcher.creator.model.creator.Workspace; +import com.skcraft.launcher.creator.model.swing.ListingType; +import com.skcraft.launcher.creator.model.swing.ManifestEntryTableModel; +import com.skcraft.launcher.dialog.ProgressDialog; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.util.SwingExecutor; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; +import java.util.stream.Collectors; + +public class GenerateListingController { + + private final GenerateListingDialog dialog; + private final Workspace workspace; + private final List manifestEntries; + private final ListeningExecutorService executor; + private final ManifestEntryTableModel manifestTableModel; + + public GenerateListingController(GenerateListingDialog dialog, Workspace workspace, List manifestEntries, ListeningExecutorService executor) { + this.dialog = dialog; + this.workspace = workspace; + this.manifestEntries = manifestEntries; + this.executor = executor; + + this.manifestTableModel = new ManifestEntryTableModel(manifestEntries); + dialog.getManifestsTable().setModel(manifestTableModel); + dialog.getManifestsTableAdjuster().adjustColumns(); + + initListeners(); + + setListingType(workspace.getPackageListingType()); + } + + public void setOutputDir(File dir) { + dialog.getDestDirField().setPath(dir.getAbsolutePath()); + } + + public void setListingType(ListingType type) { + dialog.getListingTypeCombo().setSelectedItem(type); + } + + public void show() { + dialog.setVisible(true); + } + + public Optional getManifestFromIndex(int selectedIndex) { + if (selectedIndex >= 0) { + ManifestEntry manifest = manifestEntries.get(selectedIndex); + if (manifest != null) { + return Optional.fromNullable(manifest); + } + } + return Optional.absent(); + } + + public Optional getSelectedManifest() { + JTable table = dialog.getManifestsTable(); + int selectedIndex = table.getSelectedRow(); + if (selectedIndex >= 0) { + selectedIndex = table.convertRowIndexToModel(selectedIndex); + ManifestEntry manifest = manifestEntries.get(selectedIndex); + if (manifest != null) { + return Optional.fromNullable(manifest); + } + } + + SwingHelper.showErrorDialog(dialog, "Please select a modpack from the list.", "Error"); + return Optional.absent(); + } + + private void updateManifestEntryInTable(ManifestEntry manifestEntry) { + int index = manifestEntries.indexOf(manifestEntry); + if (index >= 0) { + manifestTableModel.fireTableRowsUpdated(index, index); + } + } + + private void initListeners() { + dialog.getManifestsTable().addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + if (e.getClickCount() == 2) { + JTable table = (JTable) e.getSource(); + Point point = e.getPoint(); + int selectedIndex = table.rowAtPoint(point); + if (selectedIndex >= 0) { + selectedIndex = table.convertRowIndexToModel(selectedIndex); + Optional optional = getManifestFromIndex(selectedIndex); + if (optional.isPresent()) { + if (showModifyDialog(optional.get())) { + updateManifestEntryInTable(optional.get()); + } + } + } + } + } + }); + + dialog.getListingTypeCombo().addItemListener(e -> { + ListingType type = (ListingType) e.getItem(); + dialog.getGameKeyWarning().setVisible(!type.isGameKeyCompatible()); + }); + + dialog.getEditManifestButton().addActionListener(e -> { + Optional optional = getSelectedManifest(); + if (optional.isPresent()) { + if (showModifyDialog(optional.get())) { + updateManifestEntryInTable(optional.get()); + } + } + }); + + dialog.getGenerateButton().addActionListener(e -> tryGenerate()); + + dialog.getCancelButton().addActionListener(e -> dialog.dispose()); + } + + private boolean showModifyDialog(ManifestEntry manifestEntry) { + ManifestEntryDialog modifyDialog = new ManifestEntryDialog(dialog); + modifyDialog.setTitle("Modify " + manifestEntry.getManifestInfo().getLocation()); + ManifestEntryController controller = new ManifestEntryController(modifyDialog, manifestEntry); + return controller.show(); + } + + private boolean tryGenerate() { + String path = dialog.getDestDirField().getPath().trim(); + + if (path.isEmpty()) { + SwingHelper.showErrorDialog(dialog, "A directory must be entered.", "Error"); + return false; + } + + List selected = manifestEntries.stream() + .filter(ManifestEntry::isSelected) + .sorted() + .collect(Collectors.toCollection(Lists::newArrayList)); + + if (selected.isEmpty()) { + SwingHelper.showErrorDialog(dialog, "At least one modpack must be selected to appear in the package list.", "Error"); + return false; + } + + ListingType listingType = (ListingType) dialog.getListingTypeCombo().getSelectedItem(); + File destDir = new File(path); + destDir.mkdirs(); + File file = new File(destDir, listingType.getFilename()); + + workspace.setPackageListingEntries(selected); + workspace.setPackageListingType(listingType); + Persistence.commitAndForget(workspace); + + SettableProgress progress = new SettableProgress("Generating package listing...", -1); + + Deferred deferred = Deferreds.makeDeferred(executor.submit(() -> listingType.generate(selected))) + .thenTap(() -> progress.set("Deleting older package listing files...", -1)) + .thenApply(input -> { + for (ListingType otherListingType : ListingType.values()) { + File f = new File(destDir, otherListingType.getFilename()); + if (f.exists()) { + f.delete(); + } + } + + return input; + }) + .thenTap(() -> progress.set("Writing package listing to disk...", -1)) + .thenApply(input -> { + try { + Files.write(input, file, Charset.forName("UTF-8")); + return file; + } catch (IOException e) { + throw new RuntimeException("Failed to write package listing file to disk", e); + } + }) + .handleAsync(v -> { + if (listingType.isGameKeyCompatible()) { + SwingHelper.showMessageDialog(dialog, "Successfully generated package listing.", "Success", null, JOptionPane.INFORMATION_MESSAGE); + } else { + SwingHelper.showMessageDialog(dialog, "Successfully generated package listing.\n\n" + + "Note that any modpacks with game keys set were not added.", + "Success", null, JOptionPane.INFORMATION_MESSAGE); + } + dialog.dispose(); + SwingHelper.browseDir(destDir, dialog); + }, ex -> {}, SwingExecutor.INSTANCE); + + ProgressDialog.showProgress(dialog, deferred, progress, "Writing package listing...", "Writing package listing..."); + SwingHelper.addErrorDialogCallback(dialog, deferred); + + return true; + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/ManifestEntryController.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/ManifestEntryController.java new file mode 100644 index 0000000..c5cc42e --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/ManifestEntryController.java @@ -0,0 +1,55 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.controller; + +import com.skcraft.launcher.creator.dialog.ManifestEntryDialog; +import com.skcraft.launcher.creator.model.creator.ManifestEntry; +import com.skcraft.launcher.swing.SwingHelper; + +public class ManifestEntryController { + + private final ManifestEntryDialog dialog; + private final ManifestEntry manifestEntry; + + private boolean save; + + public ManifestEntryController(ManifestEntryDialog dialog, ManifestEntry manifestEntry) { + this.dialog = dialog; + this.manifestEntry = manifestEntry; + + initListeners(); + copyFrom(); + } + + private void copyFrom() { + dialog.getIncludeCheck().setSelected(manifestEntry.isSelected()); + dialog.getPrioritySpinner().setValue(manifestEntry.getManifestInfo().getPriority()); + SwingHelper.setTextAndResetCaret(dialog.getGameKeysText(), SwingHelper.listToLines(manifestEntry.getGameKeys())); + } + + private void copyTo() { + manifestEntry.setSelected(dialog.getIncludeCheck().isSelected()); + manifestEntry.getManifestInfo().setPriority((Integer) dialog.getPrioritySpinner().getValue()); + manifestEntry.setGameKeys(SwingHelper.linesToList(dialog.getGameKeysText().getText())); + } + + public boolean show() { + dialog.setVisible(true); + return save; + } + + private void initListeners() { + dialog.getOkButton().addActionListener(e -> { + copyTo(); + save = true; + dialog.dispose(); + }); + + dialog.getCancelButton().addActionListener(e -> dialog.dispose()); + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/PackManagerController.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/PackManagerController.java new file mode 100644 index 0000000..582fef0 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/PackManagerController.java @@ -0,0 +1,752 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.controller; + +import com.google.common.base.Optional; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.skcraft.concurrency.Deferred; +import com.skcraft.concurrency.Deferreds; +import com.skcraft.concurrency.SettableProgress; +import com.skcraft.launcher.InstanceList; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.auth.OfflineSession; +import com.skcraft.launcher.auth.Session; +import com.skcraft.launcher.builder.BuilderConfig; +import com.skcraft.launcher.builder.FnPatternList; +import com.skcraft.launcher.creator.model.creator.Pack; +import com.skcraft.launcher.creator.controller.task.*; +import com.skcraft.launcher.creator.dialog.*; +import com.skcraft.launcher.creator.dialog.BuildDialog.BuildOptions; +import com.skcraft.launcher.creator.dialog.DeployServerDialog.DeployOptions; +import com.skcraft.launcher.creator.model.creator.ManifestEntry; +import com.skcraft.launcher.creator.model.creator.Problem; +import com.skcraft.launcher.creator.model.creator.Workspace; +import com.skcraft.launcher.creator.model.swing.PackTableModel; +import com.skcraft.launcher.creator.server.TestServer; +import com.skcraft.launcher.creator.server.TestServerBuilder; +import com.skcraft.launcher.creator.swing.PackDirectoryFilter; +import com.skcraft.launcher.dialog.ConfigurationDialog; +import com.skcraft.launcher.dialog.ConsoleFrame; +import com.skcraft.launcher.dialog.ProgressDialog; +import com.skcraft.launcher.model.modpack.LaunchModifier; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.swing.PopupMouseAdapter; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.util.MorePaths; +import com.skcraft.launcher.util.SwingExecutor; +import lombok.Getter; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.regex.Pattern; + +public class PackManagerController { + + private static final DateFormat VERSION_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + private static final Pattern FILENAME_SANITIZE = Pattern.compile("[^a-z0-9_\\-\\.]+"); + + @Getter private final File workspaceDir; + @Getter private final File workspaceFile; + @Getter private final File dataDir; + @Getter private final File distDir; + @Getter private File webRoot; + @Getter private Workspace workspace; + @Getter private final Launcher launcher; + @Getter private final ListeningExecutorService executor; + @Getter private final TestServer testServer; + + private File lastServerDestDir; + + private final PackManagerFrame frame; + private PackTableModel packTableModel; + + public PackManagerController(PackManagerFrame frame, File workspaceDir) throws IOException { + this.workspaceDir = workspaceDir; + this.dataDir = Workspace.getDataDir(workspaceDir); + workspaceFile = Workspace.getWorkspaceFile(workspaceDir); + + this.distDir = new File(workspaceDir, "_upload"); + File launcherDir = new File(dataDir, "staging/launcher"); + this.webRoot = new File(dataDir, "staging/www"); + + launcherDir.mkdirs(); + webRoot.mkdirs(); + + this.launcher = new Launcher(launcherDir); + this.executor = launcher.getExecutor(); + this.frame = frame; + + TestServerBuilder builder = new TestServerBuilder(); + builder.setBaseDir(webRoot); + builder.setPort(0); + testServer = builder.build(); + } + + public void show() { + frame.setVisible(true); + frame.setTitle("Modpack Creator - [" + workspaceDir.getAbsolutePath() + "]"); + + initListeners(); + loadWorkspace(); + + Deferreds.makeDeferred(executor.submit(() -> { + startServer(); + return null; + })) + .handle( + result -> { + }, + (ex) -> SwingHelper.showErrorDialog(frame, "Failed to start a local web server. You will be unable to test modpacks.", "Error", ex) + ); + } + + private void startServer() throws Exception { + testServer.start(); + + launcher.getProperties().setProperty("newsUrl", "http://localhost:" + testServer.getLocalPort() + "/news.html"); + launcher.getProperties().setProperty("packageListUrl", "http://localhost:" + testServer.getLocalPort() + "/packages.json"); + launcher.getProperties().setProperty("selfUpdateUrl", "http://localhost:" + testServer.getLocalPort() + "/latest.json"); + } + + private void loadWorkspace() { + PackLoader loader = new PackLoader(); + + SettableProgress progress = new SettableProgress("Loading workspace...", -1); + + Deferred deferred = Deferreds.makeDeferred(executor.submit(() -> { + Workspace workspace = Persistence.load(workspaceFile, Workspace.class); + workspace.setDirectory(workspaceDir); + workspace.load(); + if (!workspaceFile.exists()) { + Persistence.commitAndForget(workspace); + } + this.workspace = workspace; + return workspace; + })) + .thenTap(() -> progress.observe(loader)) + .thenApply(loader) + .thenApplyAsync(packs -> { + JTable table = frame.getPackTable(); + packTableModel = new PackTableModel(packs); + table.setModel(packTableModel); + packTableModel.fireTableDataChanged(); + table.getRowSorter().toggleSortOrder(1); + if (packTableModel.getRowCount() > 0) { + table.addRowSelectionInterval(0, 0); + } + + return packs; + }, SwingExecutor.INSTANCE); + + ProgressDialog.showProgress(frame, deferred, progress, "Loading workspace...", "Loading workspace..."); + SwingHelper.addErrorDialogCallback(frame, deferred); + } + + private boolean checkPackLoaded(Pack pack) { + if (pack.isLoaded()) { + return true; + } else { + SwingHelper.showErrorDialog(frame, "The selected pack could not be loaded. You will have to remove it from the workspace or change its location.", "Error"); + return false; + } + } + + public Optional getPackFromIndex(int selectedIndex, boolean requireLoaded) { + if (selectedIndex >= 0) { + Pack pack = workspace.getPacks().get(selectedIndex); + if (pack != null && (!requireLoaded || checkPackLoaded(pack))) { + return Optional.fromNullable(pack); + } + } + return Optional.absent(); + } + + public Optional getSelectedPack(boolean requireLoaded) { + JTable table = frame.getPackTable(); + int selectedIndex = table.getSelectedRow(); + if (selectedIndex >= 0) { + selectedIndex = table.convertRowIndexToModel(selectedIndex); + Pack pack = workspace.getPacks().get(selectedIndex); + if (pack != null && (!requireLoaded || checkPackLoaded(pack))) { + return Optional.fromNullable(pack); + } + } + + SwingHelper.showErrorDialog(frame, "Please select a modpack from the list.", "Error"); + return Optional.absent(); + } + + public boolean writeWorkspace() { + try { + Persistence.commit(workspace); + return true; + } catch (IOException e) { + SwingHelper.showErrorDialog(frame, "Failed to write the current state of the workspace to disk. " + + "Any recent changes to the list of modpacks may not appear in the workspace on the next load.", "Error", e); + return false; + } + } + + public boolean writeBuilderConfig(Pack pack, BuilderConfig config) { + try { + Persistence.write(pack.getConfigFile(), config, Persistence.L2F_LIST_PRETTY_PRINTER); + return true; + } catch (IOException e) { + SwingHelper.showErrorDialog(frame, "Failed to write modpack.json to disk. Aborting.", "Error", e); + return false; + } + } + + public boolean canAddPackDir(File dir) { + try { + if (dir.exists() && !dir.isDirectory()) { + SwingHelper.showErrorDialog(frame, "The selected path is a file that already exists. It must be a directory.", "Error"); + return false; + } else if (dir.getCanonicalPath().equals(workspaceDir.getCanonicalPath())) { + SwingHelper.showErrorDialog(frame, "You cannot choose the workspace directory.", "Error"); + return false; + } else if (workspace.hasPack(dir)) { + SwingHelper.showErrorDialog(frame, "There is already a modpack in this workspace that uses that directory.", "Error"); + return false; + } else { + return true; + } + } catch (IOException e) { + SwingHelper.showErrorDialog(frame, "An unexpected error occurred while checking if the modpack being added can be added.", "Error", e); + return false; + } + } + + public boolean addPackToWorkspace(Pack pack) { + pack.load(); + + try { + File base = workspaceDir; + File child = pack.getDirectory(); + if (MorePaths.isSubDirectory(base, child)) { + pack.setLocation(MorePaths.relativize(base, child)); + } + } catch (IOException e) { + SwingHelper.showErrorDialog(frame, "An unexpected error occurred that could have been caused by the removal " + + "of the workspace or modpack directory.", "Error", e); + return false; + } + + List packs = workspace.getPacks(); + pack.setWorkspace(workspace); + packs.add(pack); + packTableModel.fireTableRowsInserted(packs.size() - 1, packs.size() - 1); + writeWorkspace(); + + return true; + } + + public void updatePackInWorkspace(Pack pack) { + List packs = workspace.getPacks(); + pack.load(); + int index = packs.indexOf(pack); + if (index >= 0) { + packTableModel.fireTableRowsUpdated(index, index); + } + writeWorkspace(); + } + + public boolean removePackFromWorkspace(Pack pack) { + if (workspace.getPacks().remove(pack)) { + packTableModel.fireTableDataChanged(); + writeWorkspace(); + return true; + } else { + return false; + } + } + + private void addDefaultConfig(BuilderConfig config) { + LaunchModifier launchModifier = new LaunchModifier(); + launchModifier.setFlags(ImmutableList.of("-Dfml.ignoreInvalidMinecraftCertificates=true")); + config.setLaunchModifier(launchModifier); + + FnPatternList userFiles = new FnPatternList(); + userFiles.setInclude(Lists.newArrayList("options.txt", "optionsshaders.txt")); + userFiles.setExclude(Lists.newArrayList()); + config.setUserFiles(userFiles); + } + + private void initListeners() { + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + try { + testServer.stop(); + } catch (Exception ignored) { + } + System.exit(0); // TODO: Proper shutdown + } + }); + + frame.getPackTable().addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + if (e.getClickCount() == 2) { + JTable table = (JTable) e.getSource(); + Point point = e.getPoint(); + int selectedIndex = table.rowAtPoint(point); + if (selectedIndex >= 0) { + selectedIndex = table.convertRowIndexToModel(selectedIndex); + Optional optional = getPackFromIndex(selectedIndex, true); + if (optional.isPresent()) { + if (e.isControlDown()) { + SwingHelper.browseDir(optional.get().getDirectory(), frame); + } else { + startTest(optional.get()); + } + } + } + } + } + }); + + frame.getPackTable().addMouseListener(new PopupMouseAdapter() { + @Override + protected void showPopup(MouseEvent e) { + JTable table = (JTable) e.getSource(); + Point point = e.getPoint(); + int selectedIndex = table.rowAtPoint(point); + if (selectedIndex >= 0) { + table.setRowSelectionInterval(selectedIndex, selectedIndex); + Optional optional = getSelectedPack(false); + if (optional.isPresent()) { + popupPackMenu(e.getComponent(), e.getX(), e.getY(), optional.get()); + } + } + } + }); + + frame.getNewPackMenuItem().addActionListener(event -> tryAddPackViaDialog()); + + frame.getNewPackAtLocationMenuItem().addActionListener(e -> tryAddPackViaDirectory(true)); + + frame.getImportPackMenuItem().addActionListener(event -> tryAddPackViaDirectory(false)); + + frame.getRemovePackItem().addActionListener(e -> { + Optional optional = getSelectedPack(false); + + if (optional.isPresent()) { + Pack pack = optional.get(); + + if (!pack.isLoaded() || SwingHelper.confirmDialog(frame, "Are you sure that you want to remove this modpack? No files will be deleted " + + "and you can later re-import the modpack using 'Add Existing'.", "Confirm")) { + removePackFromWorkspace(pack); + } + } + }); + + frame.getDeletePackItem().addActionListener(e -> { + Optional optional = getSelectedPack(false); + + if (optional.isPresent()) { + Pack pack = optional.get(); + + String input = JOptionPane.showInputDialog( + frame, + "Are you sure that you want to delete '" + pack.getDirectory().getAbsolutePath() + "'? " + + "If yes, type 'delete' below.", + "Confirm", + JOptionPane.WARNING_MESSAGE); + + if (input != null && input.replaceAll("'", "").equalsIgnoreCase("delete")) { + removePackFromWorkspace(pack); + + DirectoryDeleter deleter = new DirectoryDeleter(pack.getDirectory()); + Deferred deferred = Deferreds.makeDeferred(executor.submit(deleter), executor); + ProgressDialog.showProgress(frame, deferred, deleter, "Deleting modpack...", "Deleting modpack..."); + SwingHelper.addErrorDialogCallback(frame, deferred); + } else if (input != null) { + SwingHelper.showMessageDialog(frame, "You did not enter the correct word. Nothing was deleted.", "Failure", null, JOptionPane.INFORMATION_MESSAGE); + } + } + }); + + frame.getChangePackLocationMenuItem().addActionListener(e -> { + Optional optional = getSelectedPack(false); + + if (optional.isPresent()) { + Pack pack = optional.get(); + + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle("Choose New Folder for Pack"); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setFileFilter(new PackDirectoryFilter()); + + File dir = workspaceDir; + + do { + chooser.setCurrentDirectory(dir); + int returnVal = chooser.showOpenDialog(frame); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + dir = chooser.getSelectedFile(); + } else { + return; + } + } while (!canAddPackDir(dir)); + + pack.setLocation(dir.getAbsolutePath()); + updatePackInWorkspace(pack); + } + }); + + frame.getRefreshMenuItem().addActionListener(e -> loadWorkspace()); + + frame.getQuitMenuItem().addActionListener(e -> { + frame.dispose(); + }); + + frame.getEditConfigMenuItem().addActionListener(e -> { + Optional optional = getSelectedPack(true); + + if (optional.isPresent()) { + Pack pack = optional.get(); + File file = pack.getConfigFile(); + BuilderConfig config = Persistence.read(file, BuilderConfig.class); + + if (BuilderConfigDialog.showEditor(frame, config)) { + writeBuilderConfig(pack, config); + updatePackInWorkspace(pack); + } + } + }); + + frame.getOpenFolderMenuItem().addActionListener(e -> { + Optional optional = getSelectedPack(true); + + if (optional.isPresent()) { + SwingHelper.browseDir(optional.get().getDirectory(), frame); + } + }); + + frame.getCheckProblemsMenuItem().addActionListener(e -> { + Optional optional = getSelectedPack(true); + + if (optional.isPresent()) { + ProblemChecker checker = new ProblemChecker(optional.get()); + Deferred deferred = Deferreds.makeDeferred(executor.submit(checker), executor) + .handleAsync(this::showProblems, (ex) -> { + }, SwingExecutor.INSTANCE); + SwingHelper.addErrorDialogCallback(frame, deferred); + } + }); + + frame.getTestMenuItem().addActionListener(e -> { + Optional optional = getSelectedPack(true); + + if (optional.isPresent()) { + Pack pack = optional.get(); + startTest(pack); + } + }); + + frame.getOptionsMenuItem().addActionListener(e -> { + ConfigurationDialog configDialog = new ConfigurationDialog(frame, launcher); + configDialog.setVisible(true); + }); + + frame.getClearInstanceMenuItem().addActionListener(e -> { + DirectoryDeleter deleter = new DirectoryDeleter(launcher.getInstancesDir()); + Deferred deferred = Deferreds.makeDeferred(executor.submit(deleter), executor); + ProgressDialog.showProgress(frame, deferred, deleter, "Deleting test instances...", "Deleting test instances..."); + SwingHelper.addErrorDialogCallback(frame, deferred); + }); + + frame.getClearWebRootMenuItem().addActionListener(e -> { + DirectoryDeleter deleter = new DirectoryDeleter(webRoot); + Deferred deferred = Deferreds.makeDeferred(executor.submit(deleter), executor); + ProgressDialog.showProgress(frame, deferred, deleter, "Deleting web server files...", "Deleting web server files..."); + SwingHelper.addErrorDialogCallback(frame, deferred); + }); + + frame.getBuildMenuItem().addActionListener(e -> { + Optional optional = getSelectedPack(true); + + if (optional.isPresent()) { + Pack pack = optional.get(); + buildPack(pack); + } + }); + + frame.getDeployServerMenuItem().addActionListener(e -> { + Optional optional = getSelectedPack(true); + + if (optional.isPresent()) { + Pack pack = optional.get(); + DeployOptions options = DeployServerDialog.showDeployDialog(frame, lastServerDestDir); + + if (options != null) { + ConsoleFrame.showMessages(); + + File destDir = options.getDestDir(); + destDir.mkdirs(); + lastServerDestDir = destDir; + + ServerDeploy deploy = new ServerDeploy(pack.getSourceDir(), options); + Deferred deferred = Deferreds.makeDeferred(executor.submit(deploy), executor) + .handleAsync(r -> SwingHelper.showMessageDialog(frame, "Server deployment complete!", "Success", null, JOptionPane.INFORMATION_MESSAGE), + ex -> {}, + SwingExecutor.INSTANCE); + ProgressDialog.showProgress(frame, deferred, deploy, "Deploying files...", "Deploying server files..."); + SwingHelper.addErrorDialogCallback(frame, deferred); + } + } + }); + + frame.getGeneratePackagesMenuItem().addActionListener(e -> { + List entries = workspace.getPackageListingEntries(); + ManifestInfoEnumerator enumerator = new ManifestInfoEnumerator(distDir); + Deferred deferred = Deferreds.makeDeferred(executor.submit(() -> enumerator.apply(entries))) + .handleAsync(loaded -> { + GenerateListingDialog dialog = new GenerateListingDialog(frame); + GenerateListingController controller = new GenerateListingController(dialog, workspace, loaded, executor); + controller.setOutputDir(distDir); + controller.show(); + }, ex -> {}, SwingExecutor.INSTANCE); + ProgressDialog.showProgress(frame, deferred, new SettableProgress("Searching...", -1), "Searching for manifests...", "Searching for manifests..."); + SwingHelper.addErrorDialogCallback(frame, deferred); + }); + + frame.getOpenOutputFolderMenuItem().addActionListener(e -> SwingHelper.browseDir(distDir, frame)); + + frame.getOpenConsoleMenuItem().addActionListener(e -> { + ConsoleFrame.showMessages(); + }); + + frame.getDocsMenuItem().addActionListener(e -> { + SwingHelper.openURL("https://github.com/SKCraft/Launcher/wiki", frame); + }); + + frame.getAboutMenuItem().addActionListener(e -> { + AboutDialog.showAboutDialog(frame); + }); + + SwingHelper.addActionListeners(frame.getNewPackButton(), frame.getNewPackMenuItem().getActionListeners()); + SwingHelper.addActionListeners(frame.getImportButton(), frame.getImportPackMenuItem().getActionListeners()); + SwingHelper.addActionListeners(frame.getEditConfigButton(), frame.getEditConfigMenuItem().getActionListeners()); + SwingHelper.addActionListeners(frame.getOpenFolderButton(), frame.getOpenFolderMenuItem().getActionListeners()); + SwingHelper.addActionListeners(frame.getCheckProblemsButton(), frame.getCheckProblemsMenuItem().getActionListeners()); + SwingHelper.addActionListeners(frame.getTestButton(), frame.getTestMenuItem().getActionListeners()); + SwingHelper.addActionListeners(frame.getBuildButton(), frame.getBuildMenuItem().getActionListeners()); + } + + private void popupPackMenu(Component component, int x, int y, Pack pack) { + JPopupMenu popup = new JPopupMenu(); + JMenuItem menuItem; + + menuItem = new JMenuItem("Edit modpack.json..."); + menuItem.addActionListener(e -> frame.getEditConfigMenuItem().doClick()); + popup.add(menuItem); + + menuItem = new JMenuItem("Open Directory"); + menuItem.addActionListener(e -> frame.getOpenFolderMenuItem().doClick()); + popup.add(menuItem); + + menuItem = new JMenuItem("Check for Problems"); + menuItem.addActionListener(e -> frame.getCheckProblemsMenuItem().doClick()); + popup.add(menuItem); + + popup.addSeparator(); + + menuItem = new JMenuItem("Test"); + menuItem.addActionListener(e -> frame.getTestMenuItem().doClick()); + popup.add(menuItem); + + menuItem = new JMenuItem("Build..."); + menuItem.addActionListener(e -> frame.getBuildMenuItem().doClick()); + popup.add(menuItem); + + menuItem = new JMenuItem("Deploy Server..."); + menuItem.addActionListener(e -> frame.getDeployServerMenuItem().doClick()); + popup.add(menuItem); + + popup.addSeparator(); + + menuItem = new JMenuItem("Change Location..."); + menuItem.addActionListener(e -> frame.getChangePackLocationMenuItem().doClick()); + popup.add(menuItem); + + menuItem = new JMenuItem("Remove..."); + menuItem.addActionListener(e -> frame.getRemovePackItem().doClick()); + popup.add(menuItem); + + menuItem = new JMenuItem("Delete Forever..."); + menuItem.addActionListener(e -> frame.getDeletePackItem().doClick()); + popup.add(menuItem); + + popup.show(component, x, y); + } + + private void tryAddPackViaDialog() { + BuilderConfig config = new BuilderConfig(); + addDefaultConfig(config); + Pack pack = new Pack(); + File dir; + + do { + if (BuilderConfigDialog.showEditor(frame, config)) { + dir = new File(workspaceDir, config.getName()); + } else { + return; + } + } while (!canAddPackDir(dir)); + + pack.setLocation(dir.getAbsolutePath()); + + if (pack.getConfigFile().exists()) { + if (SwingHelper.confirmDialog(frame, "There's already an existing modpack with that name, though " + + "it is not imported into this workspace. Would you like to import it and ignore " + + "the new modpack that you just entered?", "Conflict")) { + addPackToWorkspace(pack); + } + } else { + if (writeBuilderConfig(pack, config)) { + pack.createGuideFolders(); + addPackToWorkspace(pack); + } + } + } + + private void tryAddPackViaDirectory(boolean createNew) { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle("Choose Folder for Pack"); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setFileFilter(new PackDirectoryFilter()); + + File dir = workspaceDir; + + do { + chooser.setCurrentDirectory(dir); + int returnVal = chooser.showOpenDialog(frame); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + dir = chooser.getSelectedFile(); + } else { + return; + } + } while (!canAddPackDir(dir)); + + Pack pack = new Pack(); + pack.setLocation(dir.getAbsolutePath()); + + if (pack.getConfigFile().exists()) { + if (createNew) { + if (SwingHelper.confirmDialog(frame, "There's already a modpack in that directory. Do you want to import it instead?", "Exists Already")) { + addPackToWorkspace(pack); + } + } else { + addPackToWorkspace(pack); + } + } else if (createNew || SwingHelper.confirmDialog(frame, "You've selected a directory that doesn't seem to be a modpack " + + "(at least with the directory structure this program expects). Would you like to create the files necessary to turn " + + "that folder into a modpack?", "Import Error")) { + + BuilderConfig config = new BuilderConfig(); + addDefaultConfig(config); + + if (BuilderConfigDialog.showEditor(frame, config)) { + if (writeBuilderConfig(pack, config)) { + pack.createGuideFolders(); + addPackToWorkspace(pack); + } + } + } + } + + private void startTest(Pack pack) { + Session session = new OfflineSession("Player"); + String version = generateVersionFromDate(); + + PackBuilder builder = new PackBuilder(pack, webRoot, version, "staging.json", false); + InstanceList.Enumerator enumerator = launcher.getInstances().createEnumerator(); + TestLauncher instanceLauncher = new TestLauncher(launcher, frame, pack.getCachedConfig().getName(), session); + + SettableProgress progress = new SettableProgress(builder); + + ConsoleFrame.showMessages(); + + Deferred deferred = Deferreds.makeDeferred(executor.submit(builder), executor) + .thenTap(() -> progress.set("Loading instance in test launcher...", -1)) + .thenRun(enumerator) + .thenTap(() -> progress.set("Launching", -1)) + .thenApply(instanceLauncher) + .handleAsync(result -> ConsoleFrame.hideMessages(), ex -> {}, SwingExecutor.INSTANCE); + + ProgressDialog.showProgress(frame, deferred, progress, "Setting up test instance...", "Preparing files for launch..."); + SwingHelper.addErrorDialogCallback(frame, deferred); + } + + private void buildPack(Pack pack) { + String initialVersion = generateVersionFromDate(); + BuildOptions options = BuildDialog.showBuildDialog(frame, initialVersion, generateManifestName(pack), distDir); + + if (options != null) { + ConsoleFrame.showMessages(); + PackBuilder builder = new PackBuilder(pack, options.getDestDir(), options.getVersion(), options.getManifestFilename(), false); + Deferred deferred = Deferreds.makeDeferred(executor.submit(builder), executor) + .handleAsync(result -> { + ConsoleFrame.hideMessages(); + SwingHelper.showMessageDialog(frame, "Successfully generated the package files.", "Success", null, JOptionPane.INFORMATION_MESSAGE); + SwingHelper.browseDir(options.getDestDir(), frame); + }, ex -> {}, SwingExecutor.INSTANCE); + ProgressDialog.showProgress(frame, deferred, builder, "Building modpack...", "Building modpack..."); + SwingHelper.addErrorDialogCallback(frame, deferred); + } + } + + private void showProblems(List problems) { + if (problems.isEmpty()) { + SwingHelper.showMessageDialog(frame, "No potential problems found!", "Success", null, JOptionPane.INFORMATION_MESSAGE); + } else { + ProblemViewer viewer = new ProblemViewer(frame, problems); + viewer.setVisible(true); + } + } + + public String generateManifestName(Pack pack) { + File file = pack.getConfigFile(); + if (file.exists()) { + BuilderConfig config = Persistence.read(file, BuilderConfig.class, true); + if (config != null) { + String name = Strings.nullToEmpty(config.getName()); + name = name.toLowerCase(); + name = FILENAME_SANITIZE.matcher(name).replaceAll("-"); + name = name.trim(); + if (!name.isEmpty()) { + return name + ".json"; + } + } + } + + return "my_modpack.json"; + } + + public static String generateVersionFromDate() { + Date today = Calendar.getInstance().getTime(); + return VERSION_DATE_FORMAT.format(today); + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/WelcomeController.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/WelcomeController.java new file mode 100644 index 0000000..266cca2 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/WelcomeController.java @@ -0,0 +1,204 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.controller; + +import com.google.common.base.Optional; +import com.google.common.collect.Lists; +import com.skcraft.launcher.creator.Creator; +import com.skcraft.launcher.creator.dialog.AboutDialog; +import com.skcraft.launcher.creator.dialog.PackManagerFrame; +import com.skcraft.launcher.creator.dialog.WelcomeDialog; +import com.skcraft.launcher.creator.model.creator.RecentEntry; +import com.skcraft.launcher.creator.model.creator.Workspace; +import com.skcraft.launcher.creator.model.swing.RecentListModel; +import com.skcraft.launcher.creator.swing.WorkspaceDirectoryFilter; +import com.skcraft.launcher.persistence.Persistence; +import com.skcraft.launcher.swing.PopupMouseAdapter; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.util.MorePaths; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +public class WelcomeController { + + private final WelcomeDialog dialog; + private final Creator creator; + private final RecentListModel recentListModel; + + public WelcomeController(WelcomeDialog dialog, Creator creator) { + this.dialog = dialog; + this.creator = creator; + + initListeners(); + + recentListModel = new RecentListModel(creator.getConfig().getRecentEntries()); + dialog.getRecentList().setModel(recentListModel); + } + + public void show() { + dialog.setVisible(true); + } + + private boolean openWorkspace(File dir) { + try { + PackManagerFrame frame = new PackManagerFrame(); + PackManagerController controller = new PackManagerController(frame, dir); + addRecentEntry(dir); + controller.show(); + return true; + } catch (IOException e) { + SwingHelper.showErrorDialog(dialog, "An unexpected error has occurred.", "Error", e); + return false; + } + } + + private void addRecentEntry(File dir) { + List newEntries = creator.getConfig().getRecentEntries() + .stream() + .filter(entry -> { + try { + return !MorePaths.isSamePath(entry.getPath(), dir); + } catch (IOException ignored) { + return false; + } + }) + .collect(Collectors.toCollection(Lists::newArrayList)); + + RecentEntry recent = new RecentEntry(); + recent.setPath(dir); + newEntries.add(0, recent); + + creator.getConfig().setRecentEntries(newEntries); + + Persistence.commitAndForget(creator.getConfig()); + + recentListModel.fireUpdate(); + } + + private void removeRecentEntry(RecentEntry entry) { + creator.getConfig().getRecentEntries().remove(entry); + Persistence.commitAndForget(creator.getConfig()); + + recentListModel.fireUpdate(); + } + + private Optional getSelectedRecentEntry() { + int selectedIndex = dialog.getRecentList().getSelectedIndex(); + if (selectedIndex >= 0) { + return Optional.fromNullable(creator.getConfig().getRecentEntries().get(selectedIndex)); + } else { + return Optional.absent(); + } + } + + private Optional getWorkspaceDir() { + JFileChooser chooser = new JFileChooser(); + chooser.setDialogTitle("Select Workspace Directory"); + chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setFileFilter(new WorkspaceDirectoryFilter()); + + int returnVal = chooser.showOpenDialog(dialog); + + if (returnVal == JFileChooser.APPROVE_OPTION) { + return Optional.fromNullable(chooser.getSelectedFile()); + } else { + return Optional.absent(); + } + } + + private void initListeners() { + dialog.getRecentList().addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + if (e.getClickCount() == 2) { + JList table = (JList) e.getSource(); + Point point = e.getPoint(); + int selectedIndex = table.locationToIndex(point); + if (selectedIndex >= 0) { + table.setSelectedIndex(selectedIndex); + Optional optional = getSelectedRecentEntry(); + if (optional.isPresent()) { + if (openWorkspace(optional.get().getPath())) { + dialog.dispose(); + } + } + } + } + } + }); + + dialog.getRecentList().addMouseListener(new PopupMouseAdapter() { + @Override + protected void showPopup(MouseEvent e) { + JList table = (JList) e.getSource(); + Point point = e.getPoint(); + int selectedIndex = table.locationToIndex(point); + if (selectedIndex >= 0) { + table.setSelectedIndex(selectedIndex); + Optional optional = getSelectedRecentEntry(); + if (optional.isPresent()) { + popupRecentWorkspaceMenu(e.getComponent(), e.getX(), e.getY(), optional.get()); + } + } + } + }); + + dialog.getNewButton().addActionListener(e -> { + Optional optional = getWorkspaceDir(); + if (optional.isPresent()) { + File workspaceFile = Workspace.getWorkspaceFile(optional.get()); + if (!workspaceFile.exists() || SwingHelper.confirmDialog(dialog, "There is already a workspace there. Do you want to load it?", "Existing")) { + if (openWorkspace(optional.get())) { + dialog.dispose(); + } + } + } + }); + + dialog.getOpenButton().addActionListener(e -> { + Optional optional = getWorkspaceDir(); + if (optional.isPresent()) { + File workspaceFile = Workspace.getWorkspaceFile(optional.get()); + if (workspaceFile.exists() || SwingHelper.confirmDialog(dialog, "Do you want to create a new workspace there?", "Create New")) { + if (openWorkspace(optional.get())) { + dialog.dispose(); + } + } + } + }); + + dialog.getHelpButton().addActionListener(e -> { + SwingHelper.openURL("https://github.com/SKCraft/Launcher/wiki", dialog); + }); + + dialog.getAboutButton().addActionListener(e -> { + AboutDialog.showAboutDialog(dialog); + }); + + dialog.getQuitButton().addActionListener(e -> { + dialog.dispose(); + }); + } + + private void popupRecentWorkspaceMenu(Component component, int x, int y, RecentEntry entry) { + JPopupMenu popup = new JPopupMenu(); + JMenuItem menuItem; + + menuItem = new JMenuItem("Remove"); + menuItem.addActionListener(e -> removeRecentEntry(entry)); + popup.add(menuItem); + + popup.show(component, x, y); + } + +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/DirectoryRemover.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/DirectoryDeleter.java similarity index 68% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/DirectoryRemover.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/DirectoryDeleter.java index 1ba9072..99141dd 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/DirectoryRemover.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/DirectoryDeleter.java @@ -4,7 +4,7 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools; +package com.skcraft.launcher.creator.controller.task; import com.skcraft.concurrency.ProgressObservable; import com.skcraft.launcher.LauncherException; @@ -16,21 +16,17 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; -import static com.skcraft.launcher.LauncherUtils.checkInterrupted; - -public class DirectoryRemover implements Callable, ProgressObservable { +public class DirectoryDeleter implements Callable, ProgressObservable { private final File dir; - public DirectoryRemover(File dir) { + public DirectoryDeleter(File dir) { this.dir = dir; } @Override - public DirectoryRemover call() throws Exception { - checkInterrupted(); - - Thread.sleep(1000); + public File call() throws Exception { + Thread.sleep(2000); List failures = new ArrayList(); @@ -42,10 +38,10 @@ public class DirectoryRemover implements Callable, ProgressObs } if (failures.size() > 0) { - throw new LauncherException(failures.size() + " failed to delete", failures.size() + " file(s) failed to delete."); + throw new LauncherException(failures.size() + " failed to delete", failures.size() + " file(s) could not be deleted"); } - return this; + return dir; } @Override @@ -55,6 +51,7 @@ public class DirectoryRemover implements Callable, ProgressObs @Override public String getStatus() { - return "Removing files..."; + return "Deleting files..."; } + } diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ManifestInfoEnumerator.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ManifestInfoEnumerator.java new file mode 100644 index 0000000..86f6c0f --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ManifestInfoEnumerator.java @@ -0,0 +1,66 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.controller.task; + +import com.skcraft.launcher.creator.model.creator.ManifestEntry; +import com.skcraft.launcher.model.modpack.Manifest; +import com.skcraft.launcher.model.modpack.ManifestInfo; +import com.skcraft.launcher.persistence.Persistence; + +import java.io.File; +import java.util.List; +import java.util.function.Function; + +public class ManifestInfoEnumerator implements Function, List> { + + private final File searchDir; + + public ManifestInfoEnumerator(File searchDir) { + this.searchDir = searchDir; + } + + @Override + public List apply(List entries) { + File[] files = searchDir.listFiles(f -> f.isFile() && f.getName().toLowerCase().endsWith(".json") && !f.getName().startsWith("packages.")); + + if (files != null) { + for (File file : files) { + String location = file.getName(); + Manifest manifest = Persistence.read(file, Manifest.class, true); + + if (manifest != null) { + ManifestInfo info = new ManifestInfo(); + info.setName(manifest.getName()); + info.setTitle(manifest.getTitle()); + info.setVersion(manifest.getVersion()); + info.setPriority(0); + info.setLocation(location); + + boolean found = false; + + for (ManifestEntry entry : entries) { + if (entry.getManifestInfo().getLocation().equals(location)) { + info.setPriority(entry.getManifestInfo().getPriority()); + entry.setManifestInfo(info); + found = true; + break; + } + } + + if (!found) { + ManifestEntry entry = new ManifestEntry(); + entry.setManifestInfo(info); + entries.add(entry); + } + } + } + } + + return entries; + } + +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ModpackBuilder.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/PackBuilder.java similarity index 80% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ModpackBuilder.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/PackBuilder.java index 272a315..bf33fe2 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ModpackBuilder.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/PackBuilder.java @@ -4,12 +4,13 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.controller.task; import com.skcraft.concurrency.ProgressObservable; import com.skcraft.launcher.LauncherException; import com.skcraft.launcher.LauncherUtils; import com.skcraft.launcher.builder.PackageBuilder; +import com.skcraft.launcher.creator.model.creator.Pack; import java.io.File; import java.io.IOException; @@ -17,16 +18,16 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; -public class ModpackBuilder implements Callable, ProgressObservable { +public class PackBuilder implements Callable, ProgressObservable { - private final File inputDir; + private final Pack pack; private final File outputDir; private final String version; private final String manifestFilename; private final boolean clean; - public ModpackBuilder(File inputDir, File outputDir, String version, String manifestFilename, boolean clean) { - this.inputDir = inputDir; + public PackBuilder(Pack pack, File outputDir, String version, String manifestFilename, boolean clean) { + this.pack = pack; this.outputDir = outputDir; this.version = version; this.manifestFilename = manifestFilename; @@ -34,7 +35,7 @@ public class ModpackBuilder implements Callable, ProgressObserva } @Override - public ModpackBuilder call() throws Exception { + public PackBuilder call() throws Exception { if (clean) { List failures = new ArrayList(); @@ -56,7 +57,7 @@ public class ModpackBuilder implements Callable, ProgressObserva String[] args = { "--version", version, "--manifest-dest", new File(outputDir, manifestFilename).getAbsolutePath(), - "-i", inputDir.getAbsolutePath(), + "-i", pack.getDirectory().getAbsolutePath(), "-o", outputDir.getAbsolutePath() }; PackageBuilder.main(args); diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/PackLoader.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/PackLoader.java new file mode 100644 index 0000000..df09771 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/PackLoader.java @@ -0,0 +1,57 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.controller.task; + +import com.google.common.base.Function; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.creator.model.creator.Pack; +import com.skcraft.launcher.creator.model.creator.Workspace; + +import java.util.List; + +public class PackLoader implements ProgressObservable, Function> { + + private int index; + private int size = 0; + private Pack lastPack; + + @Override + public List apply(Workspace workspace) { + List packs = workspace.getPacks(); + size = packs.size(); + + for (Pack pack : packs) { + lastPack = pack; + pack.load(); + index++; + } + + lastPack = null; + + return packs; + } + + @Override + public double getProgress() { + if (size == 0) { + return -1; + } else { + return index / (double) size; + } + } + + @Override + public String getStatus() { + Pack pack = lastPack; + if (pack != null) { + return "Loading " + pack.getDirectory().getName() + "..."; + } else { + return "Enumerating packs..."; + } + } + +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemChecker.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ProblemChecker.java similarity index 78% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemChecker.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ProblemChecker.java index 0719036..f23c1e1 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemChecker.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ProblemChecker.java @@ -4,12 +4,13 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.controller.task; import com.beust.jcommander.internal.Lists; import com.skcraft.concurrency.ProgressObservable; import com.skcraft.launcher.builder.BuilderOptions; -import com.skcraft.launcher.buildtools.BuildTools; +import com.skcraft.launcher.creator.model.creator.Pack; +import com.skcraft.launcher.creator.model.creator.Problem; import java.io.File; import java.util.List; @@ -17,51 +18,51 @@ import java.util.concurrent.Callable; public class ProblemChecker implements Callable>, ProgressObservable { - private final BuildTools buildTools; - private String status = "Checking for problems..."; + private final Pack pack; - public ProblemChecker(BuildTools buildTools) { - this.buildTools = buildTools; + public ProblemChecker(Pack pack) { + this.pack = pack; } @Override public List call() throws Exception { List problems = Lists.newArrayList(); - File inputDir = buildTools.getInputDir(); - File srcDir = buildTools.getSrcDir(); - File loadersDir = new File(inputDir, BuilderOptions.DEFAULT_LOADERS_DIRNAME); + File packDir = pack.getDirectory(); + File srcDir = pack.getSourceDir(); + + File loadersDir = new File(packDir, BuilderOptions.DEFAULT_LOADERS_DIRNAME); File modsDir = new File(srcDir, "mods"); boolean hasLoaders = hasFiles(loadersDir); boolean hasMods = hasFiles(modsDir); String[] files; - if (new File(inputDir, "_CLIENT").exists()) { + if (new File(packDir, "_CLIENT").exists()) { problems.add(new Problem("Root _CLIENT", "There's a _CLIENT folder that's not in " + "the src/ folder. Only files that are in src/ will actually appear in the " + "modpack, so you probably intended to put _CLIENT in src/.")); } - if (new File(inputDir, "_SERVER").exists()) { + if (new File(packDir, "_SERVER").exists()) { problems.add(new Problem("Root _SERVER", "There's a _SERVER folder that's not in " + "the src/ folder. Only files that are in src/ will actually appear in the " + "modpack, so you probably intended to put _SERVER in src/.")); } - if (new File(inputDir, "mods").exists()) { + if (new File(packDir, "mods").exists()) { problems.add(new Problem("Root mods", "There's a mods folder that's not in " + "the src/ folder. Only files that are in src/ will actually appear in the " + "modpack.")); } - if (new File(inputDir, "config").exists()) { + if (new File(packDir, "config").exists()) { problems.add(new Problem("Root mods", "There's a config folder that's not in " + "the src/ folder. Only files that are in src/ will actually appear in the " + "modpack.")); } - if (new File(inputDir, "version.json").exists()) { + if (new File(packDir, "version.json").exists()) { problems.add(new Problem("Legacy version.json", "There's a version.json file in the " + "project directory. If you are upgrading your modpack from an old version " + "of the launcher, then you should be able to delete version.json as it is " + @@ -83,7 +84,7 @@ public class ProblemChecker implements Callable>, ProgressObservab @Override public String getStatus() { - return status; + return "Checking for problems..."; } private static boolean hasFiles(File dir) { diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ServerDeployer.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ServerDeploy.java similarity index 83% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ServerDeployer.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ServerDeploy.java index a3aeec5..d392833 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ServerDeployer.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/ServerDeploy.java @@ -4,13 +4,13 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.controller.task; import com.skcraft.concurrency.ProgressObservable; import com.skcraft.launcher.LauncherException; import com.skcraft.launcher.LauncherUtils; import com.skcraft.launcher.builder.ServerCopyExport; -import com.skcraft.launcher.buildtools.compile.DeployServerDialog.DeployOptions; +import com.skcraft.launcher.creator.dialog.DeployServerDialog.DeployOptions; import java.io.File; import java.io.IOException; @@ -18,18 +18,18 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; -public class ServerDeployer implements Callable, ProgressObservable { +public class ServerDeploy implements Callable, ProgressObservable { private final File srcDir; private final DeployOptions options; - public ServerDeployer(File srcDir, DeployOptions options) { + public ServerDeploy(File srcDir, DeployOptions options) { this.srcDir = srcDir; this.options = options; } @Override - public ServerDeployer call() throws Exception { + public ServerDeploy call() throws Exception { File modsDir = new File(options.getDestDir(), "mods"); if (options.isCleanMods() && modsDir.isDirectory()) { diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/TestLauncher.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/TestLauncher.java new file mode 100644 index 0000000..f96e983 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/controller/task/TestLauncher.java @@ -0,0 +1,81 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.controller.task; + +import com.google.common.base.Function; +import com.google.common.base.Optional; +import com.skcraft.concurrency.ProgressObservable; +import com.skcraft.launcher.Instance; +import com.skcraft.launcher.InstanceList; +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.auth.Session; +import com.skcraft.launcher.launch.LaunchOptions; +import com.skcraft.launcher.launch.LaunchOptions.UpdatePolicy; +import com.skcraft.launcher.swing.SwingHelper; + +import java.awt.*; +import java.util.List; + +public class TestLauncher implements Function, ProgressObservable { + + private final Launcher launcher; + private final Window window; + private final String id; + private final Session session; + + public TestLauncher(Launcher launcher, Window window, String id, Session session) { + this.launcher = launcher; + this.window = window; + this.id = id; + this.session = session; + } + + private Optional findInstance(List instances) { + for (Instance instance : instances) { + if (instance.getName().equals(id)) { + return Optional.fromNullable(instance); + } + } + + return Optional.absent(); + } + + @Override + public Instance apply(InstanceList instanceList) { + Optional optional = findInstance(instanceList.getInstances()); + + if (optional.isPresent()) { + LaunchOptions options = new LaunchOptions.Builder() + .setInstance(optional.get()) + .setUpdatePolicy(UpdatePolicy.ALWAYS_UPDATE) + .setWindow(window) + .setSession(session) + .build(); + + launcher.getLaunchSupervisor().launch(options); + + return optional.get(); + } else { + SwingHelper.showErrorDialog(window, + "After generating the necessary files, it appears the modpack can't be found in the " + + "launcher. Did you change modpack.json while the launcher was launching?", "Launch Error"); + + return null; + } + } + + @Override + public double getProgress() { + return -1; + } + + @Override + public String getStatus() { + return "Launching the game..."; + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/AboutDialog.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/AboutDialog.java new file mode 100644 index 0000000..dc1468a --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/AboutDialog.java @@ -0,0 +1,77 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.dialog; + +import com.skcraft.launcher.LauncherUtils; +import com.skcraft.launcher.creator.Creator; +import com.skcraft.launcher.swing.SwingHelper; +import lombok.extern.java.Log; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.util.Properties; +import java.util.logging.Level; + +@Log +public class AboutDialog extends JDialog { + + private String version; + + public AboutDialog(Window parent) { + super(parent, "About", ModalityType.DOCUMENT_MODAL); + + try { + Properties properties = LauncherUtils.loadProperties(Creator.class, "creator.properties", "com.skcraft.creator.propertiesFile"); + version = properties.getProperty("version", "????"); + } catch (IOException e) { + log.log(Level.WARNING, "Failed to get version", e); + version = "????"; + } + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + initComponents(); + setResizable(false); + pack(); + setLocationRelativeTo(parent); + } + + private void initComponents() { + JPanel container = new JPanel(); + container.setLayout(new MigLayout("insets dialog")); + + container.add(new JLabel(SwingHelper.readImageIcon(Creator.class, "about_header.png")), "dock north"); + container.add(new JLabel("Version " + version), "wrap"); + container.add(new JLabel("Licensed under GNU General Public License, version 3."), "wrap, gapbottom unrel"); + container.add(new JLabel("Created by the SKCraft team. Visit our website!"), "wrap, gapbottom unrel"); + + JButton okButton = new JButton("OK"); + JButton sourceCodeButton = new JButton("Source Code"); + JButton skCraftButton = new JButton("Website"); + + container.add(sourceCodeButton, "span, split 3, sizegroup bttn"); + container.add(skCraftButton, "sizegroup bttn"); + container.add(okButton, "tag ok, sizegroup bttn"); + + add(container, BorderLayout.CENTER); + + getRootPane().setDefaultButton(okButton); + getRootPane().registerKeyboardAction(e -> okButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + + okButton.addActionListener(e -> dispose()); + sourceCodeButton.addActionListener(e -> SwingHelper.openURL("https://github.com/SKCraft/Launcher", this)); + skCraftButton.addActionListener(e -> SwingHelper.openURL("http://www.skcraft.com", this)); + } + + public static void showAboutDialog(Window parent) { + AboutDialog dialog = new AboutDialog(parent); + dialog.setVisible(true); + } +} + diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/BuildDialog.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/BuildDialog.java similarity index 77% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/BuildDialog.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/BuildDialog.java index 0ff94a6..3a8a036 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/BuildDialog.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/BuildDialog.java @@ -4,18 +4,18 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.dialog; import com.skcraft.launcher.swing.DirectoryField; import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.swing.TextFieldPopupMenu; import lombok.Data; import lombok.Getter; import net.miginfocom.swing.MigLayout; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.io.File; public class BuildDialog extends JDialog { @@ -23,7 +23,6 @@ public class BuildDialog extends JDialog { private final DirectoryField destDirField = new DirectoryField(); private final JTextField versionText = new JTextField(20); private final JTextField manifestFilenameText = new JTextField(30); - private final JCheckBox cleanCheck = new JCheckBox("Delete previously generated files first"); @Getter private BuildOptions options; @@ -38,6 +37,9 @@ public class BuildDialog extends JDialog { } private void initComponents() { + versionText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + manifestFilenameText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + JPanel container = new JPanel(); container.setLayout(new MigLayout("insets dialog")); @@ -50,9 +52,7 @@ public class BuildDialog extends JDialog { container.add(new JLabel("Output Directory:")); container.add(destDirField, "span"); - container.add(cleanCheck, "span, gapbottom unrel"); - - JButton buildButton = new JButton("Build..."); + JButton buildButton = new JButton("Build"); JButton cancelButton = new JButton("Cancel"); container.add(buildButton, "tag ok, span, split 2, sizegroup bttn"); @@ -60,19 +60,11 @@ public class BuildDialog extends JDialog { add(container, BorderLayout.CENTER); - buildButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - returnValue(); - } - }); + getRootPane().setDefaultButton(buildButton); + getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); - cancelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); + buildButton.addActionListener(e -> returnValue()); + cancelButton.addActionListener(e -> dispose()); } private void returnValue() { @@ -94,7 +86,7 @@ public class BuildDialog extends JDialog { return; } - options = new BuildOptions(version, manifestFilename, cleanCheck.isSelected(), new File(destDirField.getPath())); + options = new BuildOptions(version, manifestFilename, new File(destDirField.getPath())); dispose(); } @@ -111,7 +103,6 @@ public class BuildDialog extends JDialog { public static class BuildOptions { private final String version; private final String manifestFilename; - private final boolean clean; private final File destDir; } diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/BuilderConfigDialog.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/BuilderConfigDialog.java similarity index 63% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/project/BuilderConfigDialog.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/BuilderConfigDialog.java index 56193d0..36b99f3 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/BuilderConfigDialog.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/BuilderConfigDialog.java @@ -4,25 +4,24 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.project; +package com.skcraft.launcher.creator.dialog; -import com.google.common.base.Joiner; +import com.google.common.base.Strings; import com.skcraft.launcher.builder.BuilderConfig; import com.skcraft.launcher.builder.FeaturePattern; import com.skcraft.launcher.builder.FnPatternList; +import com.skcraft.launcher.creator.model.swing.FeaturePatternTableModel; import com.skcraft.launcher.model.modpack.LaunchModifier; import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.swing.TextFieldPopupMenu; import net.miginfocom.swing.MigLayout; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; public class BuilderConfigDialog extends JDialog { - private static final Joiner NEW_LINE_JOINER = Joiner.on("\n"); - private final JTextField nameText = new JTextField(20); private final JTextField titleText = new JTextField(30); private final JTextField gameVersionText = new JTextField(10); @@ -36,7 +35,7 @@ public class BuilderConfigDialog extends JDialog { private boolean saved = false; public BuilderConfigDialog(Window parent, BuilderConfig config) { - super(parent, "Edit modpack.json", ModalityType.DOCUMENT_MODAL); + super(parent, "Modpack Properties", ModalityType.DOCUMENT_MODAL); this.config = config; @@ -47,9 +46,17 @@ public class BuilderConfigDialog extends JDialog { setLocationRelativeTo(parent); copyFrom(); + + nameText.requestFocus(); } private void initComponents() { + nameText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + titleText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + gameVersionText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + launchFlagsArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + userFilesIncludeArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + launchFlagsArea.setFont(nameText.getFont()); userFilesIncludeArea.setFont(nameText.getFont()); userFilesExcludeArea.setFont(nameText.getFont()); @@ -72,33 +79,28 @@ public class BuilderConfigDialog extends JDialog { container.add(saveButton, "tag ok, span, split 2, sizegroup bttn"); container.add(cancelButton, "tag cancel, sizegroup bttn"); + getRootPane().setDefaultButton(saveButton); + getRootPane().registerKeyboardAction(event -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + add(container, BorderLayout.CENTER); - saveButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (nameText.getText().trim().isEmpty()) { - SwingHelper.showErrorDialog(BuilderConfigDialog.this, "The 'Name' field cannot be empty.", "Input Error"); - return; - } - - if (gameVersionText.getText().trim().isEmpty()) { - SwingHelper.showErrorDialog(BuilderConfigDialog.this, "The 'Game Version' field must be a Minecraft version.", "Input Error"); - return; - } - - copyTo(); - saved = true; - dispose(); + saveButton.addActionListener(e -> { + if (nameText.getText().trim().isEmpty()) { + SwingHelper.showErrorDialog(BuilderConfigDialog.this, "The 'Name' field cannot be empty.", "Input Error"); + return; } + + if (gameVersionText.getText().trim().isEmpty()) { + SwingHelper.showErrorDialog(BuilderConfigDialog.this, "The 'Game Version' field must be a Minecraft version.", "Input Error"); + return; + } + + copyTo(); + saved = true; + dispose(); }); - cancelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); + cancelButton.addActionListener(e -> dispose()); } private JPanel createMainPanel() { @@ -158,42 +160,33 @@ public class BuilderConfigDialog extends JDialog { container.add(SwingHelper.wrapScrollPane(featuresTable), "grow, w 10:100:null, gaptop 10"); - newButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - FeaturePattern pattern = new FeaturePattern(); - if (FeaturePatternDialog.showEditor(BuilderConfigDialog.this, pattern)) { - featuresModel.addFeature(pattern); - } + newButton.addActionListener(e -> { + FeaturePattern pattern = new FeaturePattern(); + if (FeaturePatternDialog.showEditor(BuilderConfigDialog.this, pattern)) { + featuresModel.addFeature(pattern); } }); - editButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - int index = featuresTable.getSelectedRow(); - if (index > -1) { - FeaturePattern pattern = featuresModel.getFeature(index); - FeaturePatternDialog.showEditor(BuilderConfigDialog.this, pattern); - featuresModel.fireTableDataChanged(); - } else { - SwingHelper.showErrorDialog(BuilderConfigDialog.this, "Select a feature first.", "No Selection"); - } + editButton.addActionListener(e -> { + int index = featuresTable.getSelectedRow(); + if (index > -1) { + FeaturePattern pattern = featuresModel.getFeature(index); + FeaturePatternDialog.showEditor(BuilderConfigDialog.this, pattern); + featuresModel.fireTableDataChanged(); + } else { + SwingHelper.showErrorDialog(BuilderConfigDialog.this, "Select a feature first.", "No Selection"); } }); - deleteButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - int index = featuresTable.getSelectedRow(); - if (index > -1) { - FeaturePattern pattern = featuresModel.getFeature(index); - if (SwingHelper.confirmDialog(BuilderConfigDialog.this, "Are you sure that you want to delete '" + pattern.getFeature().getName() + "'?", "Delete")) { - featuresModel.removeFeature(index); - } - } else { - SwingHelper.showErrorDialog(BuilderConfigDialog.this, "Select a feature first.", "No Selection"); + deleteButton.addActionListener(e -> { + int index = featuresTable.getSelectedRow(); + if (index > -1) { + FeaturePattern pattern = featuresModel.getFeature(index); + if (SwingHelper.confirmDialog(BuilderConfigDialog.this, "Are you sure that you want to delete '" + pattern.getFeature().getName() + "'?", "Delete")) { + featuresModel.removeFeature(index); } + } else { + SwingHelper.showErrorDialog(BuilderConfigDialog.this, "Select a feature first.", "No Selection"); } }); @@ -204,16 +197,16 @@ public class BuilderConfigDialog extends JDialog { SwingHelper.setTextAndResetCaret(nameText, config.getName()); SwingHelper.setTextAndResetCaret(titleText, config.getTitle()); SwingHelper.setTextAndResetCaret(gameVersionText, config.getGameVersion()); - SwingHelper.setTextAndResetCaret(launchFlagsArea, NEW_LINE_JOINER.join(config.getLaunchModifier().getFlags())); - SwingHelper.setTextAndResetCaret(userFilesIncludeArea, NEW_LINE_JOINER.join(config.getUserFiles().getInclude())); - SwingHelper.setTextAndResetCaret(userFilesExcludeArea, NEW_LINE_JOINER.join(config.getUserFiles().getExclude())); + SwingHelper.setTextAndResetCaret(launchFlagsArea, SwingHelper.listToLines(config.getLaunchModifier().getFlags())); + SwingHelper.setTextAndResetCaret(userFilesIncludeArea, SwingHelper.listToLines(config.getUserFiles().getInclude())); + SwingHelper.setTextAndResetCaret(userFilesExcludeArea, SwingHelper.listToLines(config.getUserFiles().getExclude())); featuresModel = new FeaturePatternTableModel(config.getFeatures()); featuresTable.setModel(featuresModel); } private void copyTo() { config.setName(nameText.getText().trim()); - config.setTitle(titleText.getText().trim()); + config.setTitle(Strings.emptyToNull(titleText.getText().trim())); config.setGameVersion(gameVersionText.getText().trim()); LaunchModifier launchModifier = config.getLaunchModifier(); diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/DeployServerDialog.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/DeployServerDialog.java similarity index 78% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/DeployServerDialog.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/DeployServerDialog.java index 3836a9b..b2d2e82 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/DeployServerDialog.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/DeployServerDialog.java @@ -4,7 +4,7 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.dialog; import com.skcraft.launcher.swing.DirectoryField; import com.skcraft.launcher.swing.SwingHelper; @@ -14,8 +14,7 @@ import net.miginfocom.swing.MigLayout; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.io.File; public class DeployServerDialog extends JDialog { @@ -46,7 +45,7 @@ public class DeployServerDialog extends JDialog { container.add(cleanModsCheck, "span, gapbottom unrel"); - JButton buildButton = new JButton("Deploy..."); + JButton buildButton = new JButton("Deploy"); JButton cancelButton = new JButton("Cancel"); container.add(buildButton, "tag ok, span, split 2, sizegroup bttn"); @@ -54,19 +53,11 @@ public class DeployServerDialog extends JDialog { add(container, BorderLayout.CENTER); - buildButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - returnValue(); - } - }); + getRootPane().setDefaultButton(buildButton); + getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); - cancelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); + buildButton.addActionListener(e -> returnValue()); + cancelButton.addActionListener(e -> dispose()); } private void returnValue() { @@ -88,8 +79,11 @@ public class DeployServerDialog extends JDialog { dispose(); } - public static DeployOptions showDeployDialog(Window parent) { + public static DeployOptions showDeployDialog(Window parent, File destDir) { DeployServerDialog dialog = new DeployServerDialog(parent); + if (destDir != null) { + dialog.destDirField.setPath(destDir.getAbsolutePath()); + } dialog.setVisible(true); return dialog.getOptions(); } diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternDialog.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/FeaturePatternDialog.java similarity index 74% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternDialog.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/FeaturePatternDialog.java index 8c3a682..f57a783 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternDialog.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/FeaturePatternDialog.java @@ -4,20 +4,21 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.project; +package com.skcraft.launcher.creator.dialog; import com.google.common.base.Joiner; import com.skcraft.launcher.builder.FeaturePattern; import com.skcraft.launcher.builder.FnPatternList; +import com.skcraft.launcher.creator.model.swing.RecommendationComboBoxModel; import com.skcraft.launcher.model.modpack.Feature; import com.skcraft.launcher.model.modpack.Feature.Recommendation; import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.swing.TextFieldPopupMenu; import net.miginfocom.swing.MigLayout; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; public class FeaturePatternDialog extends JDialog { @@ -48,6 +49,11 @@ public class FeaturePatternDialog extends JDialog { } private void initComponents() { + nameText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + descArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + includeArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + excludeArea.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + descArea.setFont(nameText.getFont()); includeArea.setFont(nameText.getFont()); excludeArea.setFont(nameText.getFont()); @@ -78,38 +84,33 @@ public class FeaturePatternDialog extends JDialog { container.add(okButton, "tag ok, span, split 2, sizegroup bttn"); container.add(cancelButton, "tag cancel, sizegroup bttn"); + getRootPane().setDefaultButton(okButton); + getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + add(container, BorderLayout.CENTER); - okButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - if (nameText.getText().trim().isEmpty()) { - SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Feature Name' field cannot be empty.", "Input Error"); - return; - } - - if (descArea.getText().trim().isEmpty()) { - SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Description' field cannot be empty.", "Input Error"); - return; - } - - if (includeArea.getText().trim().isEmpty()) { - SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Include Patterns' field cannot be empty.", "Input Error"); - return; - } - - copyTo(); - saved = true; - dispose(); + okButton.addActionListener(e -> { + if (nameText.getText().trim().isEmpty()) { + SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Feature Name' field cannot be empty.", "Input Error"); + return; } + + if (descArea.getText().trim().isEmpty()) { + SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Description' field cannot be empty.", "Input Error"); + return; + } + + if (includeArea.getText().trim().isEmpty()) { + SwingHelper.showErrorDialog(FeaturePatternDialog.this, "The 'Include Patterns' field cannot be empty.", "Input Error"); + return; + } + + copyTo(); + saved = true; + dispose(); }); - cancelButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); + cancelButton.addActionListener(e -> dispose()); } private void copyFrom() { diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternTable.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/FeaturePatternTable.java similarity index 52% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternTable.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/FeaturePatternTable.java index 9e69caf..333699a 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternTable.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/FeaturePatternTable.java @@ -4,21 +4,13 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.project; +package com.skcraft.launcher.creator.dialog; + +import com.skcraft.launcher.swing.DefaultTable; -import javax.swing.*; import javax.swing.table.TableModel; -import java.awt.*; -class FeaturePatternTable extends JTable { - - public FeaturePatternTable() { - setShowGrid(false); - setRowHeight((int) (Math.max(getRowHeight(), new JCheckBox().getPreferredSize().getHeight() - 2))); - setIntercellSpacing(new Dimension(0, 0)); - setFillsViewportHeight(true); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - } +class FeaturePatternTable extends DefaultTable { @Override public void setModel(TableModel dataModel) { diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/GenerateListingDialog.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/GenerateListingDialog.java new file mode 100644 index 0000000..d778271 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/GenerateListingDialog.java @@ -0,0 +1,77 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.dialog; + +import com.skcraft.launcher.creator.Creator; +import com.skcraft.launcher.creator.model.swing.ListingType; +import com.skcraft.launcher.creator.model.swing.ListingTypeComboBoxModel; +import com.skcraft.launcher.swing.DefaultTable; +import com.skcraft.launcher.swing.DirectoryField; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.swing.TableColumnAdjuster; +import lombok.Getter; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; + +public class GenerateListingDialog extends JDialog { + + @Getter private final DirectoryField destDirField = new DirectoryField(); + @Getter private final JComboBox listingTypeCombo = new JComboBox<>(new ListingTypeComboBoxModel()); + @Getter private final JTable manifestsTable = new DefaultTable(); + @Getter private final JLabel gameKeyWarning = new JLabel("Selected listing type won't support adding modpacks using 'game keys'.", SwingHelper.readImageIcon(Creator.class, "warning_icon.png"), SwingConstants.LEFT); + + @Getter private final JButton editManifestButton = new JButton("Modify..."); + + @Getter private final JButton generateButton = new JButton("Generate"); + @Getter private final JButton cancelButton = new JButton("Cancel"); + + @Getter private final TableColumnAdjuster manifestsTableAdjuster = new TableColumnAdjuster(manifestsTable); + + public GenerateListingDialog(Window parent) { + super(parent, "Generate Package Listing", ModalityType.DOCUMENT_MODAL); + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + initComponents(); + pack(); + setLocationRelativeTo(parent); + } + + private void initComponents() { + manifestsTableAdjuster.adjustColumns(); + manifestsTableAdjuster.setDynamicAdjustment(true); + + manifestsTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + manifestsTable.setAutoCreateRowSorter(true); + + JPanel container = new JPanel(); + container.setLayout(new MigLayout("insets dialog, fill", "[grow 0][grow 100]")); + + container.add(new JLabel("Output Directory:")); + container.add(destDirField, "span"); + + container.add(new JLabel("Package Listing Type:")); + container.add(listingTypeCombo, "span"); + container.add(gameKeyWarning, "span, skip 1, hidemode 3"); + + container.add(new JLabel("Modpacks to Include:"), "span, gaptop unrel"); + container.add(SwingHelper.wrapScrollPane(manifestsTable), "grow, pushy, span, w 500:650, h 170"); + container.add(editManifestButton, "gapbottom unrel, span, split 2"); + container.add(new JLabel("Previously-selected modpacks and those in the _upload directory are the available options."), "gapbottom unrel"); + + container.add(generateButton, "tag ok, span, split 2, sizegroup bttn"); + container.add(cancelButton, "tag cancel, sizegroup bttn"); + + add(container, BorderLayout.CENTER); + + getRootPane().setDefaultButton(generateButton); + getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ManifestEntryDialog.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ManifestEntryDialog.java new file mode 100644 index 0000000..60f3cab --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ManifestEntryDialog.java @@ -0,0 +1,64 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.dialog; + +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.swing.TextFieldPopupMenu; +import lombok.Getter; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; + +public class ManifestEntryDialog extends JDialog { + + @Getter private final JSpinner prioritySpinner = new JSpinner(); + @Getter private final JTextArea gameKeysText = new JTextArea(5, 30); + @Getter private final JCheckBox includeCheck = new JCheckBox("Include in package listing"); + + @Getter private final JButton okButton = new JButton("OK"); + @Getter private final JButton cancelButton = new JButton("Cancel"); + + public ManifestEntryDialog(Window parent) { + super(parent, "Modpack Entry", ModalityType.DOCUMENT_MODAL); + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + initComponents(); + setResizable(false); + pack(); + setLocationRelativeTo(parent); + } + + private void initComponents() { + gameKeysText.setFont(prioritySpinner.getFont()); + + prioritySpinner.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + gameKeysText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + + JPanel container = new JPanel(); + container.setLayout(new MigLayout("insets dialog")); + + container.add(includeCheck, "span, gapbottom unrel"); + + container.add(new JLabel("Priority:")); + container.add(prioritySpinner, "span, split 2, w 50"); + container.add(new JLabel("(Greater is higher)")); + + container.add(new JLabel("Game Keys:")); + container.add(SwingHelper.wrapScrollPane(gameKeysText), "span"); + + container.add(okButton, "tag ok, span, split 2, sizegroup bttn, gaptop unrel"); + container.add(cancelButton, "tag cancel, sizegroup bttn"); + + add(container, BorderLayout.CENTER); + + getRootPane().setDefaultButton(okButton); + getRootPane().registerKeyboardAction(e -> cancelButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/PackManagerFrame.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/PackManagerFrame.java new file mode 100644 index 0000000..a80a60f --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/PackManagerFrame.java @@ -0,0 +1,175 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.dialog; + +import com.skcraft.launcher.creator.Creator; +import com.skcraft.launcher.swing.DefaultTable; +import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.swing.TableColumnAdjuster; +import lombok.Getter; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; + +public class PackManagerFrame extends JFrame { + + @Getter private final JButton newPackButton = new JButton("New Pack", SwingHelper.readImageIcon(Creator.class, "new.png")); + @Getter private final JButton importButton = new JButton("Add Existing", SwingHelper.readImageIcon(Creator.class, "import.png")); + @Getter private final JButton editConfigButton = new JButton("Modify", SwingHelper.readImageIcon(Creator.class, "edit.png")); + @Getter private final JButton openFolderButton = new JButton("Open", SwingHelper.readImageIcon(Creator.class, "open_folder.png")); + @Getter private final JButton checkProblemsButton = new JButton("Check", SwingHelper.readImageIcon(Creator.class, "check.png")); + @Getter private final JButton testButton = new JButton("Test", SwingHelper.readImageIcon(Creator.class, "test.png")); + @Getter private final JButton buildButton = new JButton("Build", SwingHelper.readImageIcon(Creator.class, "build.png")); + + @Getter private final JMenuItem newPackMenuItem = new JMenuItem("New Pack..."); + @Getter private final JMenuItem newPackAtLocationMenuItem = new JMenuItem("New Pack at Location..."); + @Getter private final JMenuItem importPackMenuItem = new JMenuItem("Add Existing Pack..."); + @Getter private final JMenuItem changePackLocationMenuItem = new JMenuItem("Change Pack Location..."); + @Getter private final JMenuItem refreshMenuItem = new JMenuItem("Reload Workspace"); + @Getter private final JMenuItem removePackItem = new JMenuItem("Remove Pack..."); + @Getter private final JMenuItem deletePackItem = new JMenuItem("Delete Pack Forever..."); + @Getter private final JMenuItem quitMenuItem = new JMenuItem("Exit"); + @Getter private final JMenuItem editConfigMenuItem = new JMenuItem("Edit modpack.json..."); + @Getter private final JMenuItem openFolderMenuItem = new JMenuItem("Open Directory"); + @Getter private final JMenuItem checkProblemsMenuItem = new JMenuItem("Scan for Problems..."); + @Getter private final JMenuItem testMenuItem = new JMenuItem("Test Pack"); + @Getter private final JMenuItem optionsMenuItem = new JMenuItem("Test Launcher Options..."); + @Getter private final JMenuItem clearInstanceMenuItem = new JMenuItem("Delete Test Launcher Instances"); + @Getter private final JMenuItem clearWebRootMenuItem = new JMenuItem("Empty Test Web Server"); + @Getter private final JMenuItem buildMenuItem = new JMenuItem("Build Pack..."); + @Getter private final JMenuItem deployServerMenuItem = new JMenuItem("Deploy Server..."); + @Getter private final JMenuItem generatePackagesMenuItem = new JMenuItem("Generate packages.json..."); + @Getter private final JMenuItem openOutputFolderMenuItem = new JMenuItem("Open Upload Folder"); + @Getter private final JMenuItem openConsoleMenuItem = new JMenuItem("Open Console"); + @Getter private final JMenuItem docsMenuItem = new JMenuItem("Documentation"); + @Getter private final JMenuItem aboutMenuItem = new JMenuItem("About"); + + @Getter private final JTable packTable = new DefaultTable(); + + public PackManagerFrame() { + super("Modpack Creator"); + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + initComponents(); + initMenu(); + pack(); + setLocationRelativeTo(null); + + SwingHelper.setIconImage(this, Creator.class, "icon.png"); + } + + private void initComponents() { + TableColumnAdjuster adjuster = new TableColumnAdjuster(packTable); + adjuster.adjustColumns(); + adjuster.setDynamicAdjustment(true); + + packTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + packTable.setAutoCreateRowSorter(true); + + JPanel container = new JPanel(); + container.setLayout(new MigLayout("fill, wrap 1")); + + container.add(createToolbar(), "dock north"); + container.add(SwingHelper.wrapScrollPane(packTable), "grow, span, w null:800:null"); + + add(container, BorderLayout.CENTER); + } + + private JToolBar createToolbar() { + JToolBar toolBar = new JToolBar("Toolbar"); + + toolBar.setFloatable(false); + + toolBar.add(newPackButton); + toolBar.add(importButton); + toolBar.addSeparator(); + toolBar.add(editConfigButton); + toolBar.add(openFolderButton); + toolBar.add(checkProblemsButton); + toolBar.addSeparator(); + toolBar.add(testButton); + toolBar.add(buildButton); + + return toolBar; + } + + private void initMenu() { + newPackMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK)); + newPackAtLocationMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK | Event.SHIFT_MASK)); + editConfigMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, Event.CTRL_MASK)); + openFolderMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, Event.CTRL_MASK | Event.SHIFT_MASK)); + testMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0)); + buildMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F10, Event.SHIFT_MASK)); + deployServerMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F9, Event.SHIFT_MASK)); + docsMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0)); + + JMenuBar menuBar; + JMenu menu; + + menuBar = new JMenuBar(); + + menu = new JMenu("File"); + menu.setMnemonic('f'); + menuBar.add(menu); + menu.add(newPackMenuItem); + menu.add(newPackAtLocationMenuItem); + menu.add(importPackMenuItem); + menu.addSeparator(); + menu.add(changePackLocationMenuItem); + menu.add(removePackItem); + menu.add(deletePackItem); + menu.addSeparator(); + menu.add(refreshMenuItem); + menu.addSeparator(); + menu.add(quitMenuItem); + + menu = new JMenu("Edit"); + menu.setMnemonic('e'); + menuBar.add(menu); + menu.add(editConfigMenuItem); + menu.add(openFolderMenuItem); + menu.addSeparator(); + menu.add(checkProblemsMenuItem); + + menu = new JMenu("Test"); + menu.setMnemonic('t'); + menuBar.add(menu); + menu.add(testMenuItem); + menu.addSeparator(); + menu.add(optionsMenuItem); + menu.addSeparator(); + menu.add(clearInstanceMenuItem); + menu.add(clearWebRootMenuItem); + + menu = new JMenu("Build"); + menu.setMnemonic('b'); + menuBar.add(menu); + menu.add(buildMenuItem); + menu.add(deployServerMenuItem); + menu.addSeparator(); + menu.add(generatePackagesMenuItem); + menu.addSeparator(); + menu.add(openOutputFolderMenuItem); + + menu = new JMenu("Tools"); + menu.setMnemonic('t'); + menuBar.add(menu); + menu.add(openConsoleMenuItem); + + menu = new JMenu("Help"); + menu.setMnemonic('h'); + menuBar.add(menu); + menu.add(docsMenuItem); + menu.addSeparator(); + menu.add(aboutMenuItem); + + setJMenuBar(menuBar); + } + +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemTable.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ProblemTable.java similarity index 55% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemTable.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ProblemTable.java index d73f442..0cdf8bd 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemTable.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ProblemTable.java @@ -4,21 +4,17 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.dialog; + +import com.skcraft.launcher.swing.DefaultTable; -import javax.swing.*; import javax.swing.table.TableModel; -import java.awt.*; -class ProblemTable extends JTable { +class ProblemTable extends DefaultTable { public ProblemTable() { - setShowGrid(false); - setRowHeight((int) (Math.max(getRowHeight(), new JCheckBox().getPreferredSize().getHeight() - 2))); - setIntercellSpacing(new Dimension(0, 0)); - setFillsViewportHeight(true); + super(); setTableHeader(null); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } @Override diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemViewer.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ProblemViewer.java similarity index 71% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemViewer.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ProblemViewer.java index d20bcca..03f0abe 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemViewer.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/ProblemViewer.java @@ -4,17 +4,17 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.dialog; +import com.skcraft.launcher.creator.model.creator.Problem; +import com.skcraft.launcher.creator.model.swing.ProblemTableModel; import com.skcraft.launcher.swing.SwingHelper; +import com.skcraft.launcher.swing.TextFieldPopupMenu; import net.miginfocom.swing.MigLayout; import javax.swing.*; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.util.List; public class ProblemViewer extends JDialog { @@ -37,6 +37,8 @@ public class ProblemViewer extends JDialog { } private void initComponents() { + explanationText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); + explanationText.setFont(new JTextField().getFont()); explanationText.setEditable(false); explanationText.setLineWrap(true); @@ -59,21 +61,16 @@ public class ProblemViewer extends JDialog { add(container, BorderLayout.CENTER); - problemTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { - @Override - public void valueChanged(ListSelectionEvent e) { - Problem selected = problemTableModel.getProblem(problemTable.getSelectedRow()); - if (selected != null) { - SwingHelper.setTextAndResetCaret(explanationText, selected.getExplanation()); - } + getRootPane().setDefaultButton(closeButton); + getRootPane().registerKeyboardAction(e -> closeButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + + problemTable.getSelectionModel().addListSelectionListener(e -> { + Problem selected = problemTableModel.getProblem(problemTable.getSelectedRow()); + if (selected != null) { + SwingHelper.setTextAndResetCaret(explanationText, selected.getExplanation()); } }); - closeButton.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - dispose(); - } - }); + closeButton.addActionListener(e -> dispose()); } } diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/WelcomeDialog.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/WelcomeDialog.java new file mode 100644 index 0000000..f1f2099 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/dialog/WelcomeDialog.java @@ -0,0 +1,70 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.dialog; + +import com.skcraft.launcher.creator.Creator; +import com.skcraft.launcher.creator.model.creator.RecentEntry; +import com.skcraft.launcher.creator.swing.BorderCellRenderer; +import com.skcraft.launcher.swing.SwingHelper; +import lombok.Getter; +import net.miginfocom.swing.MigLayout; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.KeyEvent; + +public class WelcomeDialog extends JFrame { + + @Getter private final JButton newButton = new JButton("New Workspace...", SwingHelper.readImageIcon(Creator.class, "new.png")); + @Getter private final JButton openButton = new JButton("Open Workspace...", SwingHelper.readImageIcon(Creator.class, "open_folder.png")); + @Getter private final JButton helpButton = new JButton("Help"); + @Getter private final JButton aboutButton = new JButton("About"); + @Getter private final JButton quitButton = new JButton("Quit"); + @Getter private final JList recentList = new JList<>(); + + public WelcomeDialog() { + super("Modpack Creator"); + + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + initComponents(); + setResizable(false); + pack(); + setLocationRelativeTo(null); + + SwingHelper.setIconImage(this, Creator.class, "icon.png"); + } + + private void initComponents() { + recentList.setCellRenderer(new BorderCellRenderer(BorderFactory.createEmptyBorder(5, 5, 5, 5))); + + JPanel container = new JPanel(); + container.setLayout(new MigLayout("insets 50 20")); + + container.add(new JLabel(SwingHelper.readImageIcon(Creator.class, "welcome_logo.png")), "wrap, gap 20 20, gapbottom 30"); + + container.add(newButton, "grow, gap 50 50, wrap"); + container.add(openButton, "grow, gap 50 50, wrap"); + + JScrollPane recentScrollPane = new JScrollPane(recentList); + recentScrollPane.setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, Color.LIGHT_GRAY)); + recentScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + container.add(recentScrollPane, "dock east, w 280, h 390"); + + JPanel buttons = new JPanel(); + buttons.setLayout(new MigLayout("insets 20", "[][]push[]")); + buttons.add(helpButton); + buttons.add(aboutButton); + buttons.add(quitButton); + container.add(buttons, "dock south"); + + add(container, BorderLayout.CENTER); + + getRootPane().registerKeyboardAction(e -> quitButton.doClick(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); + } + + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/CreatorConfig.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/CreatorConfig.java new file mode 100644 index 0000000..7e852de --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/CreatorConfig.java @@ -0,0 +1,33 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.creator; + +import com.google.common.collect.Lists; +import lombok.Data; + +import java.util.List; + +@Data +public class CreatorConfig { + + private List recentEntries = Lists.newArrayList(); + + public void setRecentEntries(List recentEntries) { + this.recentEntries = recentEntries != null ? recentEntries : Lists.newArrayList(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/ManifestEntry.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/ManifestEntry.java new file mode 100644 index 0000000..bfe51b1 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/ManifestEntry.java @@ -0,0 +1,27 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.creator; + +import com.google.common.collect.Lists; +import com.skcraft.launcher.model.modpack.ManifestInfo; +import lombok.Data; + +import java.util.List; + +@Data +public class ManifestEntry implements Comparable { + + private boolean selected = false; + private ManifestInfo manifestInfo; + private List gameKeys = Lists.newArrayList(); + + @Override + public int compareTo(ManifestEntry o) { + return manifestInfo.compareTo(o.getManifestInfo()); + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Pack.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Pack.java new file mode 100644 index 0000000..035316a --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Pack.java @@ -0,0 +1,67 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.creator; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.skcraft.launcher.builder.BuilderConfig; +import com.skcraft.launcher.builder.BuilderOptions; +import com.skcraft.launcher.creator.model.creator.Workspace; +import com.skcraft.launcher.persistence.Persistence; +import lombok.Data; + +import java.io.File; + +@Data +public class Pack { + + private String location; + @JsonIgnore private Workspace workspace; + @JsonIgnore private BuilderConfig cachedConfig; + + @JsonIgnore + public File getDirectory() { + File path = new File(location); + if (path.isAbsolute()) { + return path; + } else { + return new File(workspace.getDirectory(), location); + } + } + + @JsonIgnore + public File getLoadersDir() { + return new File(getDirectory(), "loaders"); + } + + @JsonIgnore + public File getSourceDir() { + return new File(getDirectory(), "src"); + } + + @JsonIgnore + public File getConfigFile() { + return new File(getDirectory(), BuilderOptions.DEFAULT_CONFIG_FILENAME); + } + + public void load() { + setCachedConfig(Persistence.read(getConfigFile(), BuilderConfig.class, true)); + getLoadersDir().mkdirs(); + getSourceDir().mkdirs(); + } + + public void createGuideFolders() { + new File(getSourceDir(), "config").mkdirs(); + new File(getSourceDir(), "mods").mkdirs(); + new File(getSourceDir(), "resourcepacks").mkdirs(); + } + + @JsonIgnore + public boolean isLoaded() { + return cachedConfig != null; + } + +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/Problem.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Problem.java similarity index 90% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/Problem.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Problem.java index a2227ce..fcc56ef 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/Problem.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Problem.java @@ -4,7 +4,7 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.model.creator; import lombok.Data; diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/RecentEntry.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/RecentEntry.java new file mode 100644 index 0000000..7b1b244 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/RecentEntry.java @@ -0,0 +1,27 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.creator; + +import lombok.Data; + +import java.io.File; + +@Data +public class RecentEntry { + + private File path; + + public void setPath(File path) { + this.path = path != null ? path : new File("."); + } + + @Override + public String toString() { + return path.getAbsolutePath(); + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Workspace.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Workspace.java new file mode 100644 index 0000000..6b7b1f9 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/creator/Workspace.java @@ -0,0 +1,78 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.creator; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.collect.Lists; +import com.skcraft.launcher.creator.model.swing.ListingType; +import lombok.Data; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +@Data +public class Workspace { + + public static final String DIR_NAME = ".modpacks"; + public static final String FILENAME = "workspace.json"; + + @JsonIgnore private File directory; + private List packs = Lists.newArrayList(); + private List packageListingEntries = Lists.newArrayList(); + private ListingType packageListingType = ListingType.STATIC; + + public void setPacks(List packs) { + this.packs = packs != null ? packs : Lists.newArrayList(); + } + + public void setPackageListingEntries(List entries) { + this.packageListingEntries = entries != null ? entries : Lists.newArrayList(); + } + + public void setPackageListingType(ListingType packageListingType) { + this.packageListingType = packageListingType != null ? packageListingType : ListingType.STATIC; + } + + public boolean hasPack(File dir) { + for (Pack pack : packs) { + try { + if (pack.getDirectory().getCanonicalPath().equals(dir.getCanonicalPath())) { + return true; + } + } catch (IOException ignored) { + } + } + + return false; + } + + public void load() { + for (Pack pack : getPacks()) { + pack.setWorkspace(this); + } + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + public static File getDataDir(File workspaceDir) { + return new File(workspaceDir, DIR_NAME); + } + + public static File getWorkspaceFile(File workspaceDir) { + return new File(getDataDir(workspaceDir), FILENAME); + } + +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternTableModel.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/FeaturePatternTableModel.java similarity index 95% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternTableModel.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/FeaturePatternTableModel.java index bf84cb3..5eb2879 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/FeaturePatternTableModel.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/FeaturePatternTableModel.java @@ -4,14 +4,14 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.project; +package com.skcraft.launcher.creator.model.swing; import com.skcraft.launcher.builder.FeaturePattern; import javax.swing.table.AbstractTableModel; import java.util.List; -class FeaturePatternTableModel extends AbstractTableModel { +public class FeaturePatternTableModel extends AbstractTableModel { private final List features; diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ListingType.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ListingType.java new file mode 100644 index 0000000..7ddd8eb --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ListingType.java @@ -0,0 +1,96 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.swing; + +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; +import com.skcraft.launcher.creator.model.creator.ManifestEntry; +import com.skcraft.launcher.model.modpack.ManifestInfo; +import com.skcraft.launcher.model.modpack.PackageList; +import com.skcraft.launcher.persistence.Persistence; +import lombok.Getter; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +public enum ListingType { + + STATIC("packages.json (static)", false, "packages.json") { + @Override + public String generate(List entries) throws IOException { + PackageList list = new PackageList(); + list.setPackages(Lists.newArrayList()); + list.setMinimumVersion(PackageList.MIN_VERSION); + for (ManifestEntry entry : entries) { + if (entry.getGameKeys().isEmpty()) { + list.getPackages().add(entry.getManifestInfo()); + } + } + return Persistence.writeValueAsString(list, Persistence.L2F_LIST_PRETTY_PRINTER); + } + }, + PHP("packages.php (requires PHP on web server) ", true, "packages.php") { + @Override + public String generate(List entries) throws IOException { + StringBuilder builder = new StringBuilder(); + builder.append(" keys = entry.getGameKeys(); + + if (!keys.isEmpty()) { + builder.append("if (count(array_intersect([").append(escapeKeys(keys)).append("], $keys)) > 0)\r\n"); + } + + builder.append("$packages[] = [\r\n"); + builder.append(" 'name' => '").append(escape(info.getName())).append("',\r\n"); + if (info.getTitle() != null) { + builder.append(" 'title' => '").append(escape(info.getTitle())).append("',\r\n"); + } + builder.append(" 'version' => '").append(escape(info.getVersion())).append("',\r\n"); + builder.append(" 'priority' => ").append(info.getPriority()).append(",\r\n"); + builder.append(" 'location' => '").append(escape(info.getLocation())).append("',\r\n"); + builder.append("];\r\n\r\n"); + } + + builder.append("$out = ['minimumVersion' => ").append(PackageList.MIN_VERSION).append(", 'packages' => $packages];\r\n"); + builder.append("header('Content-Type: text/plain; charset=utf-8');\r\n"); + builder.append("echo json_encode($out);\r\n"); + return builder.toString(); + } + + private String escape(String t) { + return t.replaceAll("[\\\\\"']", "\\$0").replace("\r", "\\r").replace("\n", "\\n"); + } + + private String escapeKeys(List list) { + return Joiner.on(", ").join(list.stream().map(s -> "'" + escape(s.toLowerCase()) + "'").collect(Collectors.toList())); + } + }; + + @Getter private final String name; + @Getter private final boolean gameKeyCompatible; + @Getter private final String filename; + + ListingType(String name, boolean gameKeyCompatible, String filename) { + this.name = name; + this.gameKeyCompatible = gameKeyCompatible; + this.filename = filename; + } + + @Override + public String toString() { + return name; + } + + public abstract String generate(List entries) throws IOException; + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ListingTypeComboBoxModel.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ListingTypeComboBoxModel.java new file mode 100644 index 0000000..2c1ca28 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ListingTypeComboBoxModel.java @@ -0,0 +1,35 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.swing; + +import javax.swing.*; + +public class ListingTypeComboBoxModel extends AbstractListModel implements ComboBoxModel { + + private ListingType selection = ListingType.STATIC; + + @Override + public void setSelectedItem(Object anItem) { + selection = (ListingType) anItem; + } + + @Override + public ListingType getSelectedItem() { + return selection; + } + + @Override + public int getSize() { + return ListingType.values().length; + } + + @Override + public ListingType getElementAt(int index) { + return ListingType.values()[index]; + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ManifestEntryTableModel.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ManifestEntryTableModel.java new file mode 100644 index 0000000..304fc2b --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ManifestEntryTableModel.java @@ -0,0 +1,122 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.swing; + +import com.google.common.base.Joiner; +import com.skcraft.launcher.creator.model.creator.ManifestEntry; + +import javax.swing.table.AbstractTableModel; +import java.util.List; + +public class ManifestEntryTableModel extends AbstractTableModel { + + private final Joiner GAME_KEY_JOINER = Joiner.on(", "); + private final List entries; + + public ManifestEntryTableModel(List entries) { + this.entries = entries; + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return ""; + case 1: + return "Modpack"; + case 2: + return "Version"; + case 3: + return "Priority"; + case 4: + return "Location"; + case 5: + return "Game Keys"; + default: + return null; + } + } + + @Override + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: + return Boolean.class; + case 1: + return String.class; + case 2: + return String.class; + case 3: + return Integer.class; + case 4: + return String.class; + case 5: + return String.class; + default: + return null; + } + } + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + entries.get(rowIndex).setSelected((Boolean) value); + break; + default: + break; + } + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + switch (columnIndex) { + case 0: + return true; + default: + return false; + } + } + + @Override + public int getRowCount() { + return entries.size(); + } + + @Override + public int getColumnCount() { + return 6; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + ManifestEntry entry = entries.get(rowIndex); + + switch (columnIndex) { + case 0: + return entry.isSelected(); + case 1: + String title = entry.getManifestInfo().getTitle(); + if (title != null) { + return title; + } else { + return entry.getManifestInfo().getName(); + } + case 2: + return entry.getManifestInfo().getVersion(); + case 3: + return entry.getManifestInfo().getPriority(); + case 4: + return entry.getManifestInfo().getLocation(); + case 5: + List gameKeys = entry.getGameKeys(); + return gameKeys != null ? GAME_KEY_JOINER.join(gameKeys) : ""; + default: + return null; + } + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/PackTableModel.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/PackTableModel.java new file mode 100644 index 0000000..11f9a06 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/PackTableModel.java @@ -0,0 +1,101 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.swing; + +import com.skcraft.launcher.Launcher; +import com.skcraft.launcher.builder.BuilderConfig; +import com.skcraft.launcher.creator.model.creator.Pack; +import com.skcraft.launcher.creator.dialog.ProblemViewer; +import com.skcraft.launcher.swing.SwingHelper; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import java.util.List; + +public class PackTableModel extends AbstractTableModel { + + private final ImageIcon instanceIcon; + private final ImageIcon warningIcon; + private final List packs; + + public PackTableModel(List packs) { + this.packs = packs; + + instanceIcon = SwingHelper.readImageIconScaled(Launcher.class, "instance_icon.png", 16, 16); + warningIcon = SwingHelper.readImageIcon(ProblemViewer.class, "warning_icon.png"); + } + + @Override + public String getColumnName(int columnIndex) { + switch (columnIndex) { + case 0: + return ""; + case 1: + return "Name"; + case 2: + return "Title"; + case 3: + return "Game Version"; + case 4: + return "Location"; + default: + return null; + } + } + + @Override + public Class getColumnClass(int columnIndex) { + switch (columnIndex) { + case 0: + return ImageIcon.class; + default: + return String.class; + } + } + + @Override + public void setValueAt(Object value, int rowIndex, int columnIndex) { + } + + @Override + public boolean isCellEditable(int rowIndex, int columnIndex) { + return false; + } + + @Override + public int getRowCount() { + return packs.size(); + } + + @Override + public int getColumnCount() { + return 5; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + Pack pack = packs.get(rowIndex); + + BuilderConfig config = pack.getCachedConfig(); + + switch (columnIndex) { + case 0: + return config != null ? instanceIcon : warningIcon; + case 1: + return config != null ? config.getName() : ""; + case 2: + return config != null ? config.getTitle() : "?"; + case 3: + return config != null ? config.getGameVersion() : "?"; + case 4: + return pack.getLocation(); + default: + return null; + } + } + +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemTableModel.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ProblemTableModel.java similarity index 85% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemTableModel.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ProblemTableModel.java index 3d290ab..28d01df 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/compile/ProblemTableModel.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/ProblemTableModel.java @@ -4,20 +4,22 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.compile; +package com.skcraft.launcher.creator.model.swing; +import com.skcraft.launcher.creator.Creator; +import com.skcraft.launcher.creator.model.creator.Problem; import com.skcraft.launcher.swing.SwingHelper; import javax.swing.*; import javax.swing.table.AbstractTableModel; import java.util.List; -class ProblemTableModel extends AbstractTableModel { +public class ProblemTableModel extends AbstractTableModel { private static final ImageIcon WARNING_ICON; static { - WARNING_ICON = SwingHelper.readImageIcon(ProblemTableModel.class, "warning_icon.png"); + WARNING_ICON = SwingHelper.readImageIcon(Creator.class, "warning_icon.png"); } private final List problems; diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/RecentListModel.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/RecentListModel.java new file mode 100644 index 0000000..2852147 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/RecentListModel.java @@ -0,0 +1,35 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.model.swing; + +import com.skcraft.launcher.creator.model.creator.RecentEntry; + +import javax.swing.*; +import java.util.List; + +public class RecentListModel extends AbstractListModel { + + private final List recentEntries; + + public RecentListModel(List recentEntries) { + this.recentEntries = recentEntries; + } + + @Override + public int getSize() { + return recentEntries.size(); + } + + @Override + public RecentEntry getElementAt(int index) { + return recentEntries.get(index); + } + + public void fireUpdate() { + fireContentsChanged(this, 0, Integer.MAX_VALUE); + } +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/RecommendationComboBoxModel.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/RecommendationComboBoxModel.java similarity index 84% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/project/RecommendationComboBoxModel.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/RecommendationComboBoxModel.java index 04bbd5f..2bd78c6 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/project/RecommendationComboBoxModel.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/model/swing/RecommendationComboBoxModel.java @@ -4,13 +4,13 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.project; +package com.skcraft.launcher.creator.model.swing; import com.skcraft.launcher.model.modpack.Feature.Recommendation; import javax.swing.*; -class RecommendationComboBoxModel extends AbstractListModel implements ComboBoxModel { +public class RecommendationComboBoxModel extends AbstractListModel implements ComboBoxModel { private Recommendation selection; diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/http/LatestHandler.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/LatestHandler.java similarity index 96% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/http/LatestHandler.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/server/LatestHandler.java index 19a805d..aa4f752 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/http/LatestHandler.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/LatestHandler.java @@ -4,7 +4,7 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.http; +package com.skcraft.launcher.creator.server; import com.fasterxml.jackson.databind.ObjectMapper; import com.skcraft.launcher.selfupdate.LatestVersionInfo; diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/http/NewsHandler.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/NewsHandler.java similarity index 96% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/http/NewsHandler.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/server/NewsHandler.java index 763c5b2..2e5e607 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/http/NewsHandler.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/NewsHandler.java @@ -4,7 +4,7 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.http; +package com.skcraft.launcher.creator.server; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/http/PackagesHandler.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/PackagesHandler.java similarity index 98% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/http/PackagesHandler.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/server/PackagesHandler.java index 4ea72b6..6fb0b2d 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/http/PackagesHandler.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/PackagesHandler.java @@ -4,7 +4,7 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.http; +package com.skcraft.launcher.creator.server; import com.beust.jcommander.internal.Lists; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/server/TestServer.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/TestServer.java new file mode 100644 index 0000000..a2750e1 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/TestServer.java @@ -0,0 +1,32 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.server; + +import lombok.Getter; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; + +public class TestServer { + + @Getter private final Server server; + + public TestServer(Server server) { + this.server = server; + } + + public void start() throws Exception { + getServer().start(); + } + + public int getLocalPort() { + return ((ServerConnector) server.getConnectors()[0]).getLocalPort(); + } + + public void stop() throws Exception { + getServer().stop(); + } +} diff --git a/build-tools/src/main/java/com/skcraft/launcher/buildtools/http/LocalHttpServerBuilder.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/TestServerBuilder.java similarity index 89% rename from build-tools/src/main/java/com/skcraft/launcher/buildtools/http/LocalHttpServerBuilder.java rename to creator-tools/src/main/java/com/skcraft/launcher/creator/server/TestServerBuilder.java index ee10495..d8ad19d 100644 --- a/build-tools/src/main/java/com/skcraft/launcher/buildtools/http/LocalHttpServerBuilder.java +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/server/TestServerBuilder.java @@ -4,7 +4,7 @@ * Please see LICENSE.txt for license information. */ -package com.skcraft.launcher.buildtools.http; +package com.skcraft.launcher.creator.server; import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jetty.server.Handler; @@ -16,7 +16,7 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler; import java.io.File; -public class LocalHttpServerBuilder { +public class TestServerBuilder { private File baseDir = new File("."); private int port = 28888; @@ -25,7 +25,7 @@ public class LocalHttpServerBuilder { return baseDir; } - public LocalHttpServerBuilder setBaseDir(File baseDir) { + public TestServerBuilder setBaseDir(File baseDir) { this.baseDir = baseDir; return this; } @@ -34,12 +34,12 @@ public class LocalHttpServerBuilder { return port; } - public LocalHttpServerBuilder setPort(int port) { + public TestServerBuilder setPort(int port) { this.port = port; return this; } - public Server build() throws Exception { + public TestServer build() { Server server = new Server(port); ObjectMapper mapper = new ObjectMapper(); @@ -72,7 +72,7 @@ public class LocalHttpServerBuilder { server.setHandler(gzip); gzip.setHandler(contexts); - return server; + return new TestServer(server); } } diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/BorderCellRenderer.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/BorderCellRenderer.java new file mode 100644 index 0000000..00ca509 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/BorderCellRenderer.java @@ -0,0 +1,28 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.swing; + +import javax.swing.*; +import javax.swing.border.Border; +import java.awt.*; + +public class BorderCellRenderer implements ListCellRenderer { + + private final Border border; + private final DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer(); + + public BorderCellRenderer(Border border) { + this.border = border; + } + + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { + JLabel renderer = (JLabel) defaultRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + renderer.setBorder(border); + return renderer; + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/PackDirectoryFilter.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/PackDirectoryFilter.java new file mode 100644 index 0000000..333c859 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/PackDirectoryFilter.java @@ -0,0 +1,26 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.swing; + +import com.skcraft.launcher.creator.model.creator.Workspace; + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +public class PackDirectoryFilter extends FileFilter { + + @Override + public boolean accept(File f) { + return f.isDirectory() && !f.getName().equals(Workspace.DIR_NAME); + } + + @Override + public String getDescription() { + return "Directories"; + } + +} diff --git a/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/WorkspaceDirectoryFilter.java b/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/WorkspaceDirectoryFilter.java new file mode 100644 index 0000000..4e936b5 --- /dev/null +++ b/creator-tools/src/main/java/com/skcraft/launcher/creator/swing/WorkspaceDirectoryFilter.java @@ -0,0 +1,24 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.creator.swing; + +import javax.swing.filechooser.FileFilter; +import java.io.File; + +public class WorkspaceDirectoryFilter extends FileFilter { + + @Override + public boolean accept(File f) { + return f.isDirectory(); + } + + @Override + public String getDescription() { + return "Directories"; + } + +} diff --git a/creator-tools/src/main/resources/com/skcraft/launcher/creator/about_header.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/about_header.png new file mode 100644 index 0000000000000000000000000000000000000000..098600638881912443f752c7cea6169251afb090 GIT binary patch literal 39221 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 L+fb_>;TZEDT|=rP literal 0 HcmV?d00001 diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/build.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/build.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/build.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/build.png diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/check.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/check.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/check.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/check.png diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/clean.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/clean.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/clean.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/clean.png diff --git a/creator-tools/src/main/resources/com/skcraft/launcher/creator/creator.properties b/creator-tools/src/main/resources/com/skcraft/launcher/creator/creator.properties new file mode 100644 index 0000000..4b5c12b --- /dev/null +++ b/creator-tools/src/main/resources/com/skcraft/launcher/creator/creator.properties @@ -0,0 +1,7 @@ +# +# SK's Minecraft Launcher +# Copyright (C) 2010-2014 Albert Pham and contributors +# Please see LICENSE.txt for license information. +# + +version=${project.version} diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/edit.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/edit.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/edit.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/edit.png diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/help.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/help.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/help.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/help.png diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/icon.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/icon.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/icon.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/icon.png diff --git a/creator-tools/src/main/resources/com/skcraft/launcher/creator/import.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/import.png new file mode 100644 index 0000000000000000000000000000000000000000..e044513a190ae519715a5d37d65f14d3cf8bbdc2 GIT binary patch literal 540 zcmV+%0^|LOP)P000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x0ntfBK~zW$-Br6v13?r$vssfh5fMc!L`We>1R;`Nh=L;c0on*Q7FPKM zTMMxX77C(huaIECLSh*RSQx}cEo=-CgPZq`Gn)td%02>uxXj}|&OK*$GT{RMY4na0^}G-7iYfPp@1MF#iLK!0zvCg&C(oJ^tl@Y>EanxJy6ZCk9gR1OtqH%2F(&%e59TM?yL zhJ1L9*4sxHk7_EX&R)Q5oB`4c3y0BpgwAf>*b&J`X`O%>!_UGqLD4AP-!Iw|7uLNEtgVxQ{Fqd(&lVMIFR^!2yQK*HZzIpWfm9` zL4pCEvYaI3|JMHK93=9^%?!k&@J)@O_5SGwuEV$%;Z@>77~=dd|Kqt3d_>*6Ed6hn z7ZwMsPDEf0#R2b}w&2i-uvYNMSiB!PE9wk|62dt;ZhJEwaJ$E2o2C71dKq=r;}5KQ eycd$cjr{>>0eBf46Nx4O0000P000>X1^@s6#OZ}&00006VoOIv0RI60 z0RN!9r;`8x0%A!-K~zW$?Nv=^6HyTU-X?LkN!`}8P0dA9d$1x@FQNx)L5dQuVnGlq z2$f#MgE#55ppb*$#gia8hzQ~-RH`R0{vm?KlVGc)Ql-R%tZp{h=ggALOG>RV4dTHt zvv22{mv8o)*&pEl?_cYH@GnT-LIMKUDc(aKAaAs%2;-cXM7a@c^*mVLC~%X`kCCru zyI>(A@8VQDg@n&2)4?z3@9Rfb7f6v0H?Kn2l&dy_Ya$mCj`JS2by}QQ&2m;LiN{TB zjWD*6>l-EOIYS?X7I*}##u?N2;CWbA1wCMs2O|{o8B`JKgAaCQtxp1~h|o%%A+NDC z@Tnr2_)$xDW@+G4MLI*hF3Vpj(vdr=O4t9+GVrM!gf3j{4cmx?7^MW+ri;8P8A#>& z@YzvRd|!>3mYGZsnVB=00>@Mfkk3X|=wyJrWqU(ce|a&6iWKNEda!@{C`x4uFLEz^ zjwgBh>(RDn;?i@KT8?@Jm@(126SL!2FmvDnhrS=~)dyyjMiS#^T3NH!7JRfro&qi~ z93A%lW&{oFBSjUrg10MK!sL`{*YlBidbR!PEliu6EtBu|W-*=^^At2^fp2yJC+|!W zEWaxy0WnS5=co(2Df3%%C8D%xh-a{vUxE?TG1xr_r|KYATt=~6q%@l-)s=AgCq8j| zVuy4CA5i-+aEnsMuYn7me=tq3Z}NWIgI_qiup|W1J{8Y^Tci|tnX0SdYTHVfBAHH_ zj^jR*0tY!><5j{hJ)WAs(Y6vM^#M`bJ T#QMU900000NkvXXu0mjfHtaM& literal 0 HcmV?d00001 diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/open_folder.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/open_folder.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/open_folder.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/open_folder.png diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/options.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/options.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/options.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/options.png diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/server.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/server.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/server.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/server.png diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/test.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/test.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/test.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/test.png diff --git a/build-tools/src/main/resources/com/skcraft/launcher/buildtools/compile/warning_icon.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/warning_icon.png similarity index 100% rename from build-tools/src/main/resources/com/skcraft/launcher/buildtools/compile/warning_icon.png rename to creator-tools/src/main/resources/com/skcraft/launcher/creator/warning_icon.png diff --git a/creator-tools/src/main/resources/com/skcraft/launcher/creator/welcome_logo.png b/creator-tools/src/main/resources/com/skcraft/launcher/creator/welcome_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..cc1602d3f590951a7d5db3f03f56797fedcb553a GIT binary patch literal 14735 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 pG>cjB_>+@(FtWr369w! and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +public interface Callback { + + void handle(T value); + +} diff --git a/launcher/src/main/java/com/skcraft/concurrency/Deferred.java b/launcher/src/main/java/com/skcraft/concurrency/Deferred.java new file mode 100644 index 0000000..22c3900 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/concurrency/Deferred.java @@ -0,0 +1,142 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +import com.google.common.base.Function; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; + +import java.util.concurrent.Callable; + +/** + * An extension of {@link ListenableFuture} that provides convenience methods + * to register functions that are triggered after upon the completion of + * the underlying task. + * + *

Dependent functions are executed using the "default" executor which + * is specified when {@code Deferred} is first created, unless + * the async variants are used to register the function.

+ * + * @param The type returned + */ +public interface Deferred extends ListenableFuture { + + /** + * Returns a new Deferred that represents the asynchronous computation + * of the given action, which is only executed upon the normal completion + * of this Deferred in the default executor. + * + * @param task The task + * @param The return type of the task + * @return The new Deferred + */ + Deferred thenRun(Callable task); + + /** + * Returns a new Deferred that represents the asynchronous computation + * of the given action, which is only executed upon the normal completion + * of this Deferred in the provided executor. + * + * @param task The task + * @param The return type of the task + * @return The new Deferred + */ + Deferred thenRunAsync(Callable task, ListeningExecutorService executor); + + /** + * Returns a new Deferred that represents the asynchronous execution + * of the given action, which is only executed upon the normal completion + * of this Deferred in the provided executor. + * + * @param task The task + * @return The new Deferred + */ + Deferred thenRun(Runnable task); + + /** + * Returns a new Deferred that represents the asynchronous execution + * of the given action, which is only executed upon the normal completion + * of this Deferred in the default executor. + * + * @param task The task + * @return The new Deferred + */ + Deferred thenRunAsync(Runnable task, ListeningExecutorService executor); + + /** + * Returns a new Deferred that represents the asynchronous execution + * of the given action as a side effect that does not change the value + * passed between the prior Deferred to any dependent Deferred instances. + * + *

The given action is only executed in the default executor upon the + * normal completion of this Deferred.

+ * + * @param task The task + * @return The new Deferred + */ + Deferred thenTap(Runnable task); + + /** + * Returns a new Deferred that represents the asynchronous execution + * of the given action as a side effect that does not change the value + * passed between the prior Deferred to any dependent Deferred instances. + * + *

The given action is only executed in the provided executor upon the + * normal completion of this Deferred.

+ * + * @param task The task + * @return The new Deferred + */ + Deferred thenTapAsync(Runnable task, ListeningExecutorService executor); + + /** + * Returns a new Deferred that represents the asynchronous execution + * of the given function, which transforms the value of the previous + * Deferred into a new value. + * + *

The given action is only executed in the default executor upon the + * normal completion of this Deferred.

+ * + * @param function The function + * @return The new Deferred + */ + Deferred thenApply(Function function); + + /** + * Returns a new Deferred that represents the asynchronous execution + * of the given function, which transforms the value of the previous + * Deferred into a new value. + * + *

The given action is only executed in the provided executor upon the + * normal completion of this Deferred.

+ * + * @param function The function + * @return The new Deferred + */ + Deferred thenApplyAsync(Function function, ListeningExecutorService executor); + + /** + * Adds callbacks that are executed asynchronously on success or failure + * using the default executor. + * + * @param onSuccess The success callback + * @param onFailure The failure callback + * @return The Deferred + */ + Deferred handle(Callback onSuccess, Callback onFailure); + + /** + * Adds callbacks that are executed asynchronously on success or failure. + * + * @param onSuccess The success callback + * @param onFailure The failure callback + * @param executor The executor + * @return The Deferred + */ + Deferred handleAsync(Callback onSuccess, Callback onFailure, ListeningExecutorService executor); + +} diff --git a/launcher/src/main/java/com/skcraft/concurrency/DeferredImpl.java b/launcher/src/main/java/com/skcraft/concurrency/DeferredImpl.java new file mode 100644 index 0000000..1a2b93a --- /dev/null +++ b/launcher/src/main/java/com/skcraft/concurrency/DeferredImpl.java @@ -0,0 +1,140 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +import com.google.common.base.Function; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; + +import java.util.concurrent.*; + +class DeferredImpl implements Deferred { + + private final ListenableFuture future; + private final ListeningExecutorService defaultExecutor; + + DeferredImpl(ListenableFuture future, ListeningExecutorService defaultExecutor) { + this.future = future; + this.defaultExecutor = defaultExecutor; + } + + @Override + public Deferred thenRun(Callable task) { + return thenRunAsync(task, defaultExecutor); + } + + @Override + public Deferred thenRunAsync(final Callable task, ListeningExecutorService executor) { + return new DeferredImpl(Futures.transform(future, new Function() { + @Override + public O apply(I input) { + try { + return task.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }, executor), defaultExecutor); + } + + @Override + public Deferred thenRun(Runnable task) { + return thenRunAsync(task, defaultExecutor); + } + + @Override + public Deferred thenRunAsync(final Runnable task, ListeningExecutorService executor) { + return new DeferredImpl(Futures.transform(future, new Function() { + @Override + public Void apply(I input) { + task.run(); + return null; + } + }), defaultExecutor); + } + + @Override + public Deferred thenTap(Runnable task) { + return thenTapAsync(task, defaultExecutor); + } + + @Override + public Deferred thenTapAsync(final Runnable task, ListeningExecutorService executor) { + return thenApplyAsync(new Function() { + @Override + public I apply(I input) { + task.run(); + return input; + } + }, executor); + } + + @Override + public Deferred thenApply(Function function) { + return thenApplyAsync(function, defaultExecutor); + } + + @Override + public Deferred thenApplyAsync(Function function, ListeningExecutorService executor) { + return new DeferredImpl(Futures.transform(future, function, executor), defaultExecutor); + } + + @Override + public Deferred handle(Callback onSuccess, Callback onFailure) { + return handleAsync(onSuccess, onFailure, defaultExecutor); + } + + @Override + public Deferred handleAsync(final Callback onSuccess, final Callback onFailure, ListeningExecutorService executor) { + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(I result) { + onSuccess.handle(result); + } + + @Override + public void onFailure(Throwable t) { + onFailure.handle(t); + } + }, executor); + + return this; + } + + @Override + public void addListener(Runnable listener, Executor executor) { + future.addListener(listener, executor); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return future.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return future.isCancelled(); + } + + @Override + public boolean isDone() { + return future.isDone(); + } + + @Override + public I get() throws InterruptedException, ExecutionException { + return future.get(); + } + + @Override + public I get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return future.get(timeout, unit); + } + +} diff --git a/launcher/src/main/java/com/skcraft/concurrency/Deferreds.java b/launcher/src/main/java/com/skcraft/concurrency/Deferreds.java new file mode 100644 index 0000000..3144b37 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/concurrency/Deferreds.java @@ -0,0 +1,45 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; + +/** + * Utility class for working with Deferred. + */ +public final class Deferreds { + + private Deferreds() { + } + + /** + * Make a new Deferred from the given future, using the same thread + * executor as the default executor. + * + * @param future The future + * @param The type returned by the future + * @return A new Deferred + */ + public static Deferred makeDeferred(ListenableFuture future) { + return makeDeferred(future, MoreExecutors.sameThreadExecutor()); + } + + /** + * Make a new Deferred from the given future. + * + * @param future The future + * @param executor The default executor + * @param The type returned by the future + * @return A new Deferred + */ + public static Deferred makeDeferred(ListenableFuture future, ListeningExecutorService executor) { + return new DeferredImpl(future, executor); + } + +} diff --git a/launcher/src/main/java/com/skcraft/concurrency/SettableProgress.java b/launcher/src/main/java/com/skcraft/concurrency/SettableProgress.java new file mode 100644 index 0000000..ce8f20b --- /dev/null +++ b/launcher/src/main/java/com/skcraft/concurrency/SettableProgress.java @@ -0,0 +1,46 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.concurrency; + +public class SettableProgress implements ProgressObservable { + + private ProgressObservable delegate; + private String status = ""; + private double progress = -1; + + public SettableProgress(String status, double progress) { + this.status = status; + this.progress = progress; + } + + public SettableProgress(ProgressObservable observable) { + this.delegate = observable; + } + + public synchronized void observe(ProgressObservable observable) { + delegate = observable; + } + + public synchronized void set(String status, double progress) { + delegate = null; + this.progress = progress; + this.status = status; + } + + @Override + public double getProgress() { + ProgressObservable delegate = this.delegate; + return delegate != null ? delegate.getProgress() : progress; + } + + @Override + public String getStatus() { + ProgressObservable delegate = this.delegate; + return delegate != null ? delegate.getStatus() : status; + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/LauncherUtils.java b/launcher/src/main/java/com/skcraft/launcher/LauncherUtils.java index 0cf6e99..caf9537 100644 --- a/launcher/src/main/java/com/skcraft/launcher/LauncherUtils.java +++ b/launcher/src/main/java/com/skcraft/launcher/LauncherUtils.java @@ -42,13 +42,17 @@ public final class LauncherUtils { Properties prop = new Properties(); try { InputStream in = closer.register(clazz.getResourceAsStream(name)); - prop.load(in); - String extraPath = System.getProperty(extraProperty); - if (extraPath != null) { - log.info("Loading extra properties for " + - clazz.getCanonicalName() + ":" + name + " from " + extraPath + "..."); - in = closer.register(new BufferedInputStream(closer.register(new FileInputStream(extraPath)))); + if (in != null) { prop.load(in); + String extraPath = System.getProperty(extraProperty); + if (extraPath != null) { + log.info("Loading extra properties for " + + clazz.getCanonicalName() + ":" + name + " from " + extraPath + "..."); + in = closer.register(new BufferedInputStream(closer.register(new FileInputStream(extraPath)))); + prop.load(in); + } + } else { + throw new FileNotFoundException(); } } finally { closer.close(); diff --git a/launcher/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java b/launcher/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java index ad8f467..c2f14e9 100644 --- a/launcher/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java +++ b/launcher/src/main/java/com/skcraft/launcher/dialog/ProgressDialog.java @@ -8,6 +8,7 @@ package com.skcraft.launcher.dialog; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.skcraft.concurrency.ObservableFuture; import com.skcraft.concurrency.ProgressObservable; import com.skcraft.launcher.swing.LinedBoxPanel; @@ -156,6 +157,10 @@ public class ProgressDialog extends JDialog { } public static void showProgress(final Window owner, final ObservableFuture future, String title, String message) { + showProgress(owner, future, future, title, message); + } + + public static void showProgress(final Window owner, final ListenableFuture future, ProgressObservable observable, String title, String message) { final ProgressDialog dialog = new ProgressDialog(owner, title, message) { @Override protected void cancel() { @@ -166,7 +171,7 @@ public class ProgressDialog extends JDialog { lastDialogRef = new WeakReference(dialog); final Timer timer = new Timer(); - timer.scheduleAtFixedRate(new UpdateProgress(dialog, future), 400, 400); + timer.scheduleAtFixedRate(new UpdateProgress(dialog, observable), 400, 400); Futures.addCallback(future, new FutureCallback() { @Override diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java index e035579..6d63f48 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/ManifestInfo.java @@ -11,9 +11,20 @@ import lombok.EqualsAndHashCode; @Data @EqualsAndHashCode(callSuper = true) -public class ManifestInfo extends BaseManifest { +public class ManifestInfo extends BaseManifest implements Comparable { private String location; private int priority; + @Override + public int compareTo(ManifestInfo o) { + if (priority > o.getPriority()) { + return -1; + } else if (priority < o.getPriority()) { + return 1; + } else { + return 0; + } + } + } diff --git a/launcher/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java b/launcher/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java index 8385ff3..4268d9e 100644 --- a/launcher/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java +++ b/launcher/src/main/java/com/skcraft/launcher/model/modpack/PackageList.java @@ -13,6 +13,8 @@ import java.util.List; @Data public class PackageList { + public static final int MIN_VERSION = 1; + private int minimumVersion; private List packages; diff --git a/launcher/src/main/java/com/skcraft/launcher/persistence/Persistence.java b/launcher/src/main/java/com/skcraft/launcher/persistence/Persistence.java index 58d5646..8f1e264 100644 --- a/launcher/src/main/java/com/skcraft/launcher/persistence/Persistence.java +++ b/launcher/src/main/java/com/skcraft/launcher/persistence/Persistence.java @@ -7,6 +7,8 @@ package com.skcraft.launcher.persistence; import com.fasterxml.jackson.core.PrettyPrinter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter.Lf2SpacesIndenter; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.io.ByteSink; import com.google.common.io.ByteSource; @@ -36,8 +38,13 @@ import java.util.logging.Level; public final class Persistence { private static final ObjectMapper mapper = new ObjectMapper(); - private static final WeakHashMap bound = - new WeakHashMap(); + private static final WeakHashMap bound = new WeakHashMap(); + public static final DefaultPrettyPrinter L2F_LIST_PRETTY_PRINTER; + + static { + L2F_LIST_PRETTY_PRINTER = new DefaultPrettyPrinter(); + L2F_LIST_PRETTY_PRINTER.indentArraysWith(Lf2SpacesIndenter.instance); + } private Persistence() { } @@ -227,4 +234,19 @@ public final class Persistence { } } + /** + * Write an object to a string. + * + * @param object the object + * @param prettyPrinter a pretty printer to use, or null + * @throws java.io.IOException on I/O error + */ + public static String writeValueAsString(Object object, PrettyPrinter prettyPrinter) throws IOException { + if (prettyPrinter != null) { + return mapper.writer(prettyPrinter).writeValueAsString(object); + } else { + return mapper.writeValueAsString(object); + } + } + } diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java b/launcher/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java index 797ff05..40b91df 100644 --- a/launcher/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java +++ b/launcher/src/main/java/com/skcraft/launcher/swing/CheckboxTable.java @@ -8,17 +8,8 @@ package com.skcraft.launcher.swing; import javax.swing.*; import javax.swing.table.TableModel; -import java.awt.*; -public class CheckboxTable extends JTable { - - public CheckboxTable() { - setShowGrid(false); - setRowHeight((int) (Math.max(getRowHeight(), new JCheckBox().getPreferredSize().getHeight() - 2))); - setIntercellSpacing(new Dimension(0, 0)); - setFillsViewportHeight(true); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - } +public class CheckboxTable extends DefaultTable { @Override public void setModel(TableModel dataModel) { @@ -28,4 +19,5 @@ public class CheckboxTable extends JTable { } catch (ArrayIndexOutOfBoundsException e) { } } + } diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/DefaultTable.java b/launcher/src/main/java/com/skcraft/launcher/swing/DefaultTable.java new file mode 100644 index 0000000..01b7fef --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/DefaultTable.java @@ -0,0 +1,25 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import javax.swing.*; +import java.awt.*; + +/** + * The default table style used throughout the launcher. + */ +public class DefaultTable extends JTable { + + public DefaultTable() { + setShowGrid(false); + setRowHeight((int) (Math.max(getRowHeight(), new JCheckBox().getPreferredSize().getHeight() - 2))); + setIntercellSpacing(new Dimension(0, 0)); + setFillsViewportHeight(true); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/DirectoryField.java b/launcher/src/main/java/com/skcraft/launcher/swing/DirectoryField.java index 2feadad..55f8864 100644 --- a/launcher/src/main/java/com/skcraft/launcher/swing/DirectoryField.java +++ b/launcher/src/main/java/com/skcraft/launcher/swing/DirectoryField.java @@ -41,6 +41,8 @@ public class DirectoryField extends JPanel { browse(); } }); + + textField.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE); } public JTextField getTextField() { diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/EventQueueExecutor.java b/launcher/src/main/java/com/skcraft/launcher/swing/EventQueueExecutor.java new file mode 100644 index 0000000..3aa66f0 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/EventQueueExecutor.java @@ -0,0 +1,24 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import java.awt.*; +import java.util.concurrent.Executor; + +public class EventQueueExecutor implements Executor { + + public static final EventQueueExecutor INSTANCE = new EventQueueExecutor(); + + private EventQueueExecutor() { + } + + @Override + public void execute(Runnable runnable) { + EventQueue.invokeLater(runnable); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTable.java b/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTable.java index 878ab9e..63643ed 100644 --- a/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTable.java +++ b/launcher/src/main/java/com/skcraft/launcher/swing/InstanceTable.java @@ -6,19 +6,13 @@ package com.skcraft.launcher.swing; -import javax.swing.*; import javax.swing.table.TableModel; -import java.awt.*; -public class InstanceTable extends JTable { +public class InstanceTable extends DefaultTable { public InstanceTable() { - setShowGrid(false); - setRowHeight(Math.max(getRowHeight() + 4, 20)); - setIntercellSpacing(new Dimension(0, 0)); - setFillsViewportHeight(true); + super(); setTableHeader(null); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); } @Override diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/SwingHelper.java b/launcher/src/main/java/com/skcraft/launcher/swing/SwingHelper.java index 01f2998..f0ef9fd 100644 --- a/launcher/src/main/java/com/skcraft/launcher/swing/SwingHelper.java +++ b/launcher/src/main/java/com/skcraft/launcher/swing/SwingHelper.java @@ -6,6 +6,7 @@ package com.skcraft.launcher.swing; +import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -27,6 +28,7 @@ import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; +import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.*; import java.lang.reflect.InvocationTargetException; @@ -54,6 +56,8 @@ public final class SwingHelper { } }; + private static final Joiner NEW_LINE_JOINER = Joiner.on("\n"); + private SwingHelper() { } @@ -300,6 +304,15 @@ public final class SwingHelper { return null; } + public static Image readIconImageScaled(Class clazz, String path, int w, int h) { + BufferedImage image = readIconImage(clazz, path); + if (image != null) { + return image.getScaledInstance(w, h, Image.SCALE_SMOOTH); + } else { + return null; + } + } + public static void setIconImage(JFrame frame, Class clazz, String path) { BufferedImage image = readIconImage(clazz, path); if (image != null) { @@ -316,6 +329,15 @@ public final class SwingHelper { } } + public static ImageIcon readImageIconScaled(Class clazz, String path, int w, int h) { + Image image = readIconImageScaled(clazz, path, w, h); + if (image != null) { + return new ImageIcon(image); + } else { + return null; + } + } + /** * Focus a component. * @@ -399,6 +421,10 @@ public final class SwingHelper { return scrollPane; } + public static String listToLines(List lines) { + return SwingHelper.NEW_LINE_JOINER.join(lines); + } + public static List linesToList(String text) { String[] tokens = text.replace("\r", "\n").split("\n"); List values = Lists.newArrayList(); @@ -411,6 +437,12 @@ public final class SwingHelper { return values; } + public static void addActionListeners(AbstractButton button, ActionListener[] listeners) { + for (ActionListener listener : listeners) { + button.addActionListener(listener); + } + } + public static boolean setLookAndFeel(String lookAndFeel) { try { UIManager.setLookAndFeel(lookAndFeel); diff --git a/launcher/src/main/java/com/skcraft/launcher/swing/TableColumnAdjuster.java b/launcher/src/main/java/com/skcraft/launcher/swing/TableColumnAdjuster.java new file mode 100644 index 0000000..1a71276 --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/swing/TableColumnAdjuster.java @@ -0,0 +1,326 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.swing; + +import lombok.Getter; +import lombok.Setter; + +import javax.swing.*; +import javax.swing.event.TableModelEvent; +import javax.swing.event.TableModelListener; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumn; +import javax.swing.table.TableColumnModel; +import javax.swing.table.TableModel; +import java.awt.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import java.util.Map; + +/* + * Class to manage the widths of colunmns in a table. + * + * Various properties control how the width of the column is calculated. + * Another property controls whether column width calculation should be dynamic. + * Finally, various Actions will be added to the table to allow the user + * to customize the functionality. + * + * This class was designed to be used with tables that use an auto resize mode + * of AUTO_RESIZE_OFF. With all other modes you are constrained as the width + * of the columns must fit inside the table. So if you increase one column, one + * or more of the other columns must decrease. Because of this the resize mode + * of RESIZE_ALL_COLUMNS will work the best. + * + *

From https://tips4java.wordpress.com/2008/11/10/table-column-adjuster/

+ */ +public class TableColumnAdjuster implements PropertyChangeListener, TableModelListener { + private JTable table; + private int spacing; + private boolean isColumnHeaderIncluded; + private boolean isColumnDataIncluded; + private boolean isOnlyAdjustLarger; + private boolean isDynamicAdjustment; + @Getter @Setter + private int imageIconWidth = 25; + private int checkBoxWidth; + private Map columnSizes = new HashMap(); + + /* + * Specify the table and use default spacing + */ + public TableColumnAdjuster(JTable table) { + this(table, 16); + checkBoxWidth = (int) new JCheckBox().getPreferredSize().getWidth(); + } + + /* + * Specify the table and spacing + */ + public TableColumnAdjuster(JTable table, int spacing) { + this.table = table; + this.spacing = spacing; + setColumnHeaderIncluded(true); + setColumnDataIncluded(true); + setOnlyAdjustLarger(false); + setDynamicAdjustment(false); + } + + /** + * Set default column properties. + */ + public void setColumnProperties() { + TableModel tm = table.getModel(); + TableColumnModel tcm = table.getColumnModel(); + + for (int i = 0; i < tm.getColumnCount(); i++) { + TableColumn column = tcm.getColumn(i); + Class columnClass = tm.getColumnClass(i); + if (columnClass == ImageIcon.class) { + column.setWidth(imageIconWidth); + column.setPreferredWidth(imageIconWidth); + column.setMaxWidth(imageIconWidth); + column.setResizable(false); + } else if (columnClass == Boolean.class) { + column.setWidth(checkBoxWidth); + column.setPreferredWidth(checkBoxWidth); + column.setMaxWidth(checkBoxWidth); + column.setResizable(false); + } + } + } + + /* + * Adjust the widths of all the columns in the table + */ + public void adjustColumns() { + TableColumnModel tcm = table.getColumnModel(); + + for (int i = 0; i < tcm.getColumnCount(); i++) { + adjustColumn(i); + } + } + + /* + * Adjust the width of the specified column in the table + */ + public void adjustColumn(final int column) { + TableColumn tableColumn = table.getColumnModel().getColumn(column); + + if (!tableColumn.getResizable()) return; + + int columnHeaderWidth = getColumnHeaderWidth(column); + int columnDataWidth = getColumnDataWidth(column); + int preferredWidth = Math.max(columnHeaderWidth, columnDataWidth); + + updateTableColumn(column, preferredWidth); + } + + /* + * Calculated the width based on the column name + */ + private int getColumnHeaderWidth(int column) { + if (!isColumnHeaderIncluded) return 0; + + TableColumn tableColumn = table.getColumnModel().getColumn(column); + Object value = tableColumn.getHeaderValue(); + TableCellRenderer renderer = tableColumn.getHeaderRenderer(); + + if (renderer == null) { + renderer = table.getTableHeader().getDefaultRenderer(); + } + + Component c = renderer.getTableCellRendererComponent(table, value, false, false, -1, column); + return c.getPreferredSize().width; + } + + /* + * Calculate the width based on the widest cell renderer for the + * given column. + */ + private int getColumnDataWidth(int column) { + if (!isColumnDataIncluded) return 0; + + int preferredWidth = 0; + int maxWidth = table.getColumnModel().getColumn(column).getMaxWidth(); + + for (int row = 0; row < table.getRowCount(); row++) { + preferredWidth = Math.max(preferredWidth, getCellDataWidth(row, column)); + + // We've exceeded the maximum width, no need to check other rows + + if (preferredWidth >= maxWidth) + break; + } + + return preferredWidth; + } + + /* + * Get the preferred width for the specified cell + */ + private int getCellDataWidth(int row, int column) { + // Inovke the renderer for the cell to calculate the preferred width + + TableCellRenderer cellRenderer = table.getCellRenderer(row, column); + Component c = table.prepareRenderer(cellRenderer, row, column); + int width = c.getPreferredSize().width + table.getIntercellSpacing().width; + + return width; + } + + /* + * Update the TableColumn with the newly calculated width + */ + private void updateTableColumn(int column, int width) { + final TableColumn tableColumn = table.getColumnModel().getColumn(column); + + if (!tableColumn.getResizable()) return; + + width += spacing; + + // Don't shrink the column width + + if (isOnlyAdjustLarger) { + width = Math.max(width, tableColumn.getPreferredWidth()); + } + + columnSizes.put(tableColumn, tableColumn.getWidth()); + + table.getTableHeader().setResizingColumn(tableColumn); + tableColumn.setWidth(width); + } + + /* + * Restore the widths of the columns in the table to its previous width + */ + public void restoreColumns() { + TableColumnModel tcm = table.getColumnModel(); + + for (int i = 0; i < tcm.getColumnCount(); i++) { + restoreColumn(i); + } + } + + /* + * Restore the width of the specified column to its previous width + */ + private void restoreColumn(int column) { + TableColumn tableColumn = table.getColumnModel().getColumn(column); + Integer width = columnSizes.get(tableColumn); + + if (width != null) { + table.getTableHeader().setResizingColumn(tableColumn); + tableColumn.setWidth(width.intValue()); + } + } + + /* + * Indicates whether to include the header in the width calculation + */ + public void setColumnHeaderIncluded(boolean isColumnHeaderIncluded) { + this.isColumnHeaderIncluded = isColumnHeaderIncluded; + } + + /* + * Indicates whether to include the model data in the width calculation + */ + public void setColumnDataIncluded(boolean isColumnDataIncluded) { + this.isColumnDataIncluded = isColumnDataIncluded; + } + + /* + * Indicates whether columns can only be increased in size + */ + public void setOnlyAdjustLarger(boolean isOnlyAdjustLarger) { + this.isOnlyAdjustLarger = isOnlyAdjustLarger; + } + + /* + * Indicate whether changes to the model should cause the width to be + * dynamically recalculated. + */ + public void setDynamicAdjustment(boolean isDynamicAdjustment) { + // May need to add or remove the TableModelListener when changed + + if (this.isDynamicAdjustment != isDynamicAdjustment) { + if (isDynamicAdjustment) { + table.addPropertyChangeListener(this); + table.getModel().addTableModelListener(this); + } else { + table.removePropertyChangeListener(this); + table.getModel().removeTableModelListener(this); + } + } + + setColumnProperties(); + + this.isDynamicAdjustment = isDynamicAdjustment; + } + + // +// Implement the PropertyChangeListener +// + public void propertyChange(PropertyChangeEvent e) { + // When the TableModel changes we need to update the listeners + // and column widths + + if ("model".equals(e.getPropertyName())) { + TableModel model = (TableModel) e.getOldValue(); + model.removeTableModelListener(this); + + model = (TableModel) e.getNewValue(); + model.addTableModelListener(this); + + setColumnProperties(); + adjustColumns(); + } + } + + // +// Implement the TableModelListener +// + public void tableChanged(final TableModelEvent e) { + if (!isColumnDataIncluded) return; + + // Needed when table is sorted. + + SwingUtilities.invokeLater(new Runnable() { + public void run() { + // A cell has been updated + + int column = table.convertColumnIndexToView(e.getColumn()); + + if (e.getType() == TableModelEvent.UPDATE && column != -1) { + // Only need to worry about an increase in width for this cell + + if (isOnlyAdjustLarger) { + int row = e.getFirstRow(); + TableColumn tableColumn = table.getColumnModel().getColumn(column); + + if (tableColumn.getResizable()) { + int width = getCellDataWidth(row, column); + updateTableColumn(column, width); + } + } + + // Could be an increase of decrease so check all rows + + else { + adjustColumn(column); + } + } + + // The update affected more than one column so adjust all columns + + else { + adjustColumns(); + } + } + }); + } +} diff --git a/launcher/src/main/java/com/skcraft/launcher/util/MorePaths.java b/launcher/src/main/java/com/skcraft/launcher/util/MorePaths.java new file mode 100644 index 0000000..65b78be --- /dev/null +++ b/launcher/src/main/java/com/skcraft/launcher/util/MorePaths.java @@ -0,0 +1,45 @@ +/* + * SK's Minecraft Launcher + * Copyright (C) 2010-2014 Albert Pham and contributors + * Please see LICENSE.txt for license information. + */ + +package com.skcraft.launcher.util; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public final class MorePaths { + + private MorePaths() { + } + + public static boolean isSamePath(File a, File b) throws IOException { + return a.getCanonicalPath().equals(b.getCanonicalPath()); + } + + public static boolean isSubDirectory(File base, File child) throws IOException { + base = base.getCanonicalFile(); + child = child.getCanonicalFile(); + + File parentFile = child; + while (parentFile != null) { + if (base.equals(parentFile)) { + return true; + } + + parentFile = parentFile.getParentFile(); + } + + return false; + } + + public static String relativize(File base, File child) { + Path basePath = Paths.get(base.getAbsolutePath()); + Path childPath = Paths.get(child.getAbsolutePath()); + return basePath.relativize(childPath).toString(); + } + +} diff --git a/launcher/src/main/java/com/skcraft/launcher/util/SwingExecutor.java b/launcher/src/main/java/com/skcraft/launcher/util/SwingExecutor.java index d3e24ec..39b9be2 100644 --- a/launcher/src/main/java/com/skcraft/launcher/util/SwingExecutor.java +++ b/launcher/src/main/java/com/skcraft/launcher/util/SwingExecutor.java @@ -6,12 +6,14 @@ package com.skcraft.launcher.util; +import com.google.common.util.concurrent.AbstractListeningExecutorService; + import javax.swing.*; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.TimeUnit; -public final class SwingExecutor extends AbstractExecutorService { +public final class SwingExecutor extends AbstractListeningExecutorService { public static final SwingExecutor INSTANCE = new SwingExecutor(); @@ -23,20 +25,6 @@ public final class SwingExecutor extends AbstractExecutorService { SwingUtilities.invokeLater(runnable); } - @Override - protected RunnableFuture newTaskFor(final Callable callable) { - return new FutureTask(callable) { - @Override - public void run() { - try { - super.run(); - } catch (Throwable e) { - setException(e); - } - } - }; - } - @Override public void shutdown() { } @@ -60,4 +48,4 @@ public final class SwingExecutor extends AbstractExecutorService { public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return false; } -} \ No newline at end of file +} diff --git a/settings.gradle b/settings.gradle index a88f495..18c4ab3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,3 @@ rootProject.name = 'launcher-parent' -include 'launcher', 'launcher-fancy', 'launcher-builder', 'launcher-bootstrap', 'build-tools' +include 'launcher', 'launcher-fancy', 'launcher-builder', 'launcher-bootstrap', 'creator-tools'