Compare commits

..
49 Commits
Author SHA1 Message Date
huangyuhui 7dad8d452f Filter preview version of OptiFine 2018-07-11 13:08:08 +08:00
huangyuhui 07f8294876 Fixed mis-unbound data. GH-377 2018-07-11 12:46:02 +08:00
huanghongxun 7185cc410b Merge pull request #376 from yushijinhun/fix
Fix selected account is not loaded
2018-07-09 21:41:44 +08:00
yushijinhun 2d985af60d Fix selected account is not loaded 2018-07-09 21:34:43 +08:00
huanghongxun bee2e64482 Merge pull request #373 from yushijinhun/account-page
完善账户信息页的显示
2018-07-09 19:14:26 +08:00
huanghongxun d5dd43c0c1 Merge pull request #372 from yushijinhun/bmclapi
支持从 BMCLAPI 下载 authlib-injector
2018-07-09 19:14:00 +08:00
yushijinhun 09470eeb8a Use auto layout instead of manual sizing 2018-07-09 14:35:30 +08:00
yushijinhun 094249a6fa Set margin of fields in AccountPage 2018-07-09 14:31:41 +08:00
yushijinhun 6b5302907a [AccountPage]Display auth server url in tooltip of auth server name 2018-07-09 14:09:45 +08:00
yushijinhun adc5b08765 Fix account page overflow 2018-07-09 14:04:30 +08:00
yushijinhun 0150e2a8d7 Support downloading authlib-injector from BMCLAPI 2018-07-09 13:17:19 +08:00
huanghongxun 45eaa0c60f Merge pull request #371 from yushijinhun/fix
Refactor(WIP) & Fixes
2018-07-08 20:06:32 +08:00
yushijinhun 66ed683196 Move config load/save code to ConfigHolder(WIP) 2018-07-08 19:32:43 +08:00
yushijinhun aa4239721e Use NIO api to load/save config 2018-07-08 19:19:37 +08:00
yushijinhun 5b0b926cbd Move config serialization/deserialization to Config class 2018-07-08 19:17:53 +08:00
yushijinhun 929ce398eb AddAccountPane 使用数据绑定 & 登录时设置disable为true 2018-07-08 18:33:37 +08:00
yushijinhun b3fdbe021d 添加验证服务器界面回车代表下一步 2018-07-08 17:47:37 +08:00
huanghongxun 423bd68ce8 Merge pull request #369 from yushijinhun/fix
Fixes
2018-07-08 12:36:29 +08:00
yushijinhun 9bffc68c70 Do not reuse dialog 2018-07-08 12:20:38 +08:00
yushijinhun b7a5a60091 Improve English translations 2018-07-08 10:25:41 +08:00
yushijinhun 333b11c19f Fix uncaught JsonParseException, mentioned in #367 2018-07-08 10:11:14 +08:00
huanghongxun fbd4d1b772 Merge pull request #368 from yushijinhun/dnd
支持 DnD 方式添加验证服务器
2018-07-07 23:28:53 +08:00
yushijinhun 06a58514b3 Fix black ComboBox 2018-07-07 23:06:44 +08:00
yushijinhun 80815fc3a0 authlib-injector DnD support 2018-07-07 23:04:56 +08:00
huanghongxun b97c277a3f Merge pull request #364 from yushijinhun/refactor-ui
UI 重构 & bug 修复
2018-07-07 20:53:22 +08:00
yushijinhun 310d693e2b Remove some error cause inferences
Trampoline - High frequency of false positives.
java.lang.OutOfMemoryError - It's more likely to be a memory leak.
2018-07-07 20:51:28 +08:00
yushijinhun c1596f6a11 Replace Controllers.closeDialog with DialogCloseEvent 2018-07-07 20:50:54 +08:00
yushijinhun 76a04343d9 Use Controllers.dialog for AddAuthlibInjectorServerDialog 2018-07-07 20:30:48 +08:00
yushijinhun 25261c99f1 Remove 'class ' prefix in error messages 2018-07-07 20:30:48 +08:00
yushijinhun d0b994fa9d Move add authentication server dialog to a new file 2018-07-07 20:30:48 +08:00
yushijinhun f259601397 Each TransitionHandler instance uses its own empty pane
Using shared empty pane may cause some probleams
2018-07-07 20:30:47 +08:00
yushijinhun 87904109ca [TransitionHandler]Set mouseTransparent of the whole view
, fix bug introduced in 3fb5048f10
2018-07-07 20:30:47 +08:00
yushijinhun 785790ccc4 Move promptText of cboServers to FXML 2018-07-07 20:29:03 +08:00
yushijinhun 759921d00e Fix missing license headers 2018-07-07 20:28:59 +08:00
huangyuhui e9db183d54 Fix blank page of add authlib-injector server dialog 2018-07-07 20:08:40 +08:00
huangyuhui a853374a88 Support new MultiMC modpack 2018-07-07 00:48:48 +08:00
huangyuhui 3fb5048f10 Fix IllegalStateException in DownloadWizardProvider 2018-07-04 21:41:07 +08:00
huangyuhui 0dd2e758e8 Update READMD.md 2018-07-03 13:02:35 +08:00
huanghongxun d1a562452a Merge pull request #362 from yushijinhun/dst-x3
Add the prompt indicating the miss of DST Root CA X3.
2018-07-01 22:55:01 +08:00
yushijinhun 9205a6cbf4 Show warning when DST Root CA X3 is missing 2018-07-01 22:51:48 +08:00
yushijinhun 4c8961f39a Fix NPE when calling i18n during its initialization 2018-07-01 22:50:28 +08:00
huanghongxun 22bdc23a09 Merge pull request #361 from yushijinhun/i18n
Reconstruct I18N.
2018-07-01 22:11:23 +08:00
yushijinhun 764cd16e21 I18n for missing JavaFX error 2018-07-01 22:00:22 +08:00
yushijinhun 7583f4651d Remove I18nException 2018-07-01 21:59:16 +08:00
yushijinhun 13e931cda4 Fallback to use Locales.DEFAULT when Settings has not been initialized 2018-07-01 21:59:12 +08:00
yushijinhun 9743cf8351 Use static import for I18n.i18n 2018-07-01 21:27:30 +08:00
yushijinhun e5ee5702f8 Move i18n things to util.i18n package 2018-07-01 21:27:28 +08:00
huangyuhui 5bd5b25b77 Add debug message of system details 2018-07-01 19:18:55 +08:00
huangyuhui 55d59f3779 Fix not on FX thread 2018-07-01 19:02:32 +08:00
79 changed files with 1438 additions and 773 deletions

No files matched your search

@@ -21,7 +21,7 @@ import com.jfoenix.concurrency.JFXUtilities;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
@@ -37,9 +37,7 @@ import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public final class Launcher extends Application {
@@ -82,6 +80,10 @@ public final class Launcher extends Application {
UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args));
Logging.LOG.info("*** " + TITLE + " ***");
Logging.LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
Logging.LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
Logging.LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
Logging.LOG.info("Java Home: " + System.getProperty("java.home"));
launch(args);
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
@@ -135,19 +137,6 @@ public final class Launcher extends Application {
return result;
}
public static String i18n(String key) {
try {
return RESOURCE_BUNDLE.getString(key);
} catch (Exception e) {
Logging.LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
return key;
}
}
public static String i18n(String key, Object... formatArgs) {
return String.format(i18n(key), formatArgs);
}
public static final File MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
public static final File HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
public static final File LOG_DIRECTORY = new File(Launcher.HMCL_DIRECTORY, "logs");
@@ -155,7 +144,6 @@ public final class Launcher extends Application {
public static final String VERSION = System.getProperty("hmcl.version.override", "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@");
public static final String NAME = "HMCL";
public static final String TITLE = NAME + " " + VERSION;
public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION));
public static final IUpgrader UPGRADER = new AppDataUpgrader();
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
@@ -17,14 +17,27 @@
*/
package org.jackhuang.hmcl;
import javax.swing.*;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.logging.Level;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.swing.JOptionPane;
public final class Main {
public static void main(String[] args) {
checkJavaFX();
checkDirectoryPath();
checkDSTRootCAX3();
Launcher.main(args);
}
@@ -41,12 +54,35 @@ public final class Main {
try {
Class.forName("javafx.application.Application");
} catch (ClassNotFoundException e) {
showErrorAndExit("JavaFX is missing.\n"
+ "If you are using Java 11 or above, please downgrade to Java 8 or 10.\n"
+ "If you are using OpenJDK, please ensure OpenJFX is included.");
showErrorAndExit(i18n("fatal.missing_javafx"));
}
}
private static void checkDSTRootCAX3() {
TrustManagerFactory tmf;
try {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
} catch (NoSuchAlgorithmException | KeyStoreException e) {
LOG.log(Level.WARNING, "Failed to init TrustManagerFactory", e);
// don't know what to do here
return;
}
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
for (X509Certificate cert : ((X509TrustManager) tm).getAcceptedIssuers()) {
if ("CN=DST Root CA X3, O=Digital Signature Trust Co.".equals((cert.getSubjectDN().getName()))) {
return;
}
}
}
}
showWarningAndContinue(i18n("fatal.missing_dst_root_ca_x3"));
}
/**
* Indicates that a fatal error has occurred, and that the application cannot start.
*/
private static void showErrorAndExit(String message) {
System.err.println(message);
System.err.println("A fatal error has occurred, forcibly exiting.");
@@ -54,4 +90,13 @@ public final class Main {
System.exit(1);
}
/**
* Indicates that potential issues have been detected, and that the application may not function properly (but it can still run).
*/
private static void showWarningAndContinue(String message) {
System.err.println(message);
System.err.println("Potential issues have been detected.");
JOptionPane.showMessageDialog(null, message, "Warning", JOptionPane.WARNING_MESSAGE);
}
}
@@ -23,7 +23,6 @@ import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CredentialExpiredException;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.launch.*;
@@ -38,6 +37,7 @@ import org.jackhuang.hmcl.task.*;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.ui.LogWindow;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.*;
@@ -51,6 +51,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class LauncherHelper {
public static final LauncherHelper INSTANCE = new LauncherHelper();
@@ -106,7 +107,7 @@ public final class LauncherHelper {
}
})
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
.then(Task.of(Launcher.i18n("account.methods"), variables -> {
.then(Task.of(i18n("account.methods"), variables -> {
try {
variables.set("account", account.logIn());
} catch (CredentialExpiredException e) {
@@ -131,12 +132,12 @@ public final class LauncherHelper {
.then(variables -> {
DefaultLauncher launcher = variables.get("launcher");
if (scriptFile == null) {
return new LaunchTask<>(launcher::launch).setName(Launcher.i18n("version.launch"));
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
} else {
return new LaunchTask<>(() -> {
launcher.makeLaunchScript(scriptFile);
return null;
}).setName(Launcher.i18n("version.launch_script"));
}).setName(i18n("version.launch_script"));
}
})
.then(Task.of(variables -> {
@@ -148,12 +149,12 @@ public final class LauncherHelper {
else
launchingStepsPane.setCancel(it -> {
process.stop();
Controllers.closeDialog(it);
it.fireEvent(new DialogCloseEvent());
});
} else
Platform.runLater(() -> {
Controllers.closeDialog(launchingStepsPane);
Controllers.dialog(Launcher.i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
launchingStepsPane.fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
});
}))
@@ -177,16 +178,23 @@ public final class LauncherHelper {
// Check if the application has stopped
// because onStop will be invoked if tasks fail when the executor service shut down.
if (!Controllers.isStopped()) {
Controllers.closeDialog(launchingStepsPane);
launchingStepsPane.fireEvent(new DialogCloseEvent());
Exception ex = executor.getLastException();
if (ex != null) {
String message;
if (ex instanceof CurseCompletionException)
message = Launcher.i18n("modpack.type.curse.error");
else
message = I18nException.getStackTrace(ex);
if (ex instanceof CurseCompletionException) {
message = i18n("modpack.type.curse.error");
} else if (ex instanceof PermissionException) {
message = i18n("launch.failed.executable_permission");
} else if (ex instanceof ProcessCreationException) {
message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage();
} else if (ex instanceof NotDecompressingNativesException) {
message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage();
} else {
message = StringUtils.getStackTrace(ex);
}
Controllers.dialog(message,
scriptFile == null ? Launcher.i18n("launch.failed") : Launcher.i18n("version.launch_script.failed"),
scriptFile == null ? i18n("launch.failed") : i18n("version.launch_script.failed"),
MessageBox.ERROR_MESSAGE);
}
}
@@ -207,7 +215,7 @@ public final class LauncherHelper {
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
JavaVersion java = setting.getJavaVersion();
if (java == null) {
Controllers.dialog(Launcher.i18n("launch.wrong_javadir"), Launcher.i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
setting.setJava(null);
setting.setDefaultJavaPath(null);
java = JavaVersion.fromCurrentEnvironment();
@@ -218,10 +226,10 @@ public final class LauncherHelper {
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
// Minecraft 1.13 and later versions only support Java 8 or later.
// Terminate launching operation.
Controllers.dialog(Launcher.i18n("launch.advice.java8_1_13"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
} else {
// Most mods require Java 8 or later version.
Controllers.dialog(Launcher.i18n("launch.advice.newer_java"), Launcher.i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.error"), MessageBox.WARNING_MESSAGE, onAccept);
}
flag = true;
}
@@ -229,24 +237,24 @@ public final class LauncherHelper {
// LaunchWrapper will crash because of assuming the system class loader is an instance of URLClassLoader.
// cpw has claimed that he will make MinecraftForge of 1.13 and later versions able to run on Java 9.
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
Controllers.dialog(Launcher.i18n("launch.advice.java9"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
Controllers.dialog(i18n("launch.advice.java9"), i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
flag = true;
}
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
Controllers.dialog(Launcher.i18n("launch.advice.different_platform"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
setting.getMaxMemory() > 1.5 * 1024) {
// 1.5 * 1024 is an inaccurate number.
// Actual memory limit depends on operating system and memory.
Controllers.dialog(Launcher.i18n("launch.advice.too_large_memory_for_32bit"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
Controllers.dialog(Launcher.i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
@@ -260,8 +268,9 @@ public final class LauncherHelper {
}
public void emitStatus(LoadingState state) {
if (state == LoadingState.DONE)
Controllers.closeDialog(launchingStepsPane);
if (state == LoadingState.DONE) {
launchingStepsPane.fireEvent(new DialogCloseEvent());
}
launchingStepsPane.setTitle(state.getLocalizedMessage());
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
@@ -297,15 +306,7 @@ public final class LauncherHelper {
@Override
public void execute() throws Exception {
try {
setResult(supplier.get());
} catch (PermissionException e) {
throw new I18nException(Launcher.i18n("launch.failed.executable_permission"), e);
} catch (ProcessCreationException e) {
throw new I18nException(Launcher.i18n("launch.failed.creating_process") + e.getLocalizedMessage(), e);
} catch (NotDecompressingNativesException e) {
throw new I18nException(Launcher.i18n("launch.failed.decompressing_natives") + e.getLocalizedMessage(), e);
}
setResult(supplier.get());
}
@Override
@@ -424,10 +425,10 @@ public final class LauncherHelper {
switch (exitType) {
case JVM_ERROR:
logWindow.setTitle(Launcher.i18n("launch.failed.cannot_create_jvm"));
logWindow.setTitle(i18n("launch.failed.cannot_create_jvm"));
break;
case APPLICATION_ERROR:
logWindow.setTitle(Launcher.i18n("launch.failed.exited_abnormally"));
logWindow.setTitle(i18n("launch.failed.exited_abnormally"));
break;
}
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.Launcher;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public enum LoadingState {
DEPENDENCIES("launch.state.dependencies"),
@@ -33,6 +33,6 @@ public enum LoadingState {
}
public String getLocalizedMessage() {
return Launcher.i18n(key);
return i18n(key);
}
}
@@ -71,7 +71,7 @@ public final class Accounts {
}
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
return Settings.SETTINGS.authlibInjectorServers.stream()
return ConfigHolder.CONFIG.authlibInjectorServers.stream()
.filter(server -> url.equals(server.getUrl()))
.findFirst()
.orElseGet(() -> {
@@ -85,7 +85,7 @@ public final class Accounts {
LOG.log(Level.WARNING, "Failed to migrate authlib injector server " + url, e);
}
Settings.SETTINGS.authlibInjectorServers.add(server);
ConfigHolder.CONFIG.authlibInjectorServers.add(server);
return server;
});
}
@@ -17,12 +17,21 @@
*/
package org.jackhuang.hmcl.setting;
import java.io.File;
import java.util.Map;
import java.util.TreeMap;
import org.hildan.fxgson.creators.ObservableListCreator;
import org.hildan.fxgson.creators.ObservableMapCreator;
import org.hildan.fxgson.creators.ObservableSetCreator;
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.util.FileTypeAdapter;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import javafx.beans.property.BooleanProperty;
@@ -36,9 +45,25 @@ import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
public final class Config implements Cloneable {
private static final Gson CONFIG_GSON = new GsonBuilder()
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
.setPrettyPrinting()
.create();
public static Config fromJson(String json) throws JsonParseException {
return CONFIG_GSON.fromJson(json, Config.class);
}
@SerializedName("last")
public final StringProperty selectedProfile = new SimpleStringProperty("");
@@ -104,8 +129,12 @@ public final class Config implements Cloneable {
public final ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList();
public String toJson() {
return CONFIG_GSON.toJson(this);
}
@Override
public Config clone() {
return Settings.GSON.fromJson(Settings.GSON.toJson(this), Config.class);
return fromJson(this.toJson());
}
}
@@ -0,0 +1,65 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Logging.LOG;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.logging.Level;
import com.google.gson.JsonParseException;
public final class ConfigHolder {
private ConfigHolder() {}
public static final String CONFIG_FILENAME = "hmcl.json";
public static final Path CONFIG_PATH = Paths.get(CONFIG_FILENAME).toAbsolutePath();
public static final Config CONFIG = initSettings();
private static Config initSettings() {
Config config = new Config();
if (Files.exists(CONFIG_PATH)) {
try {
Config deserialized = Config.fromJson(new String(Files.readAllBytes(CONFIG_PATH), UTF_8));
if (deserialized == null) {
LOG.finer("Settings file is empty, use the default settings.");
} else {
config = deserialized;
}
LOG.finest("Initialized settings.");
} catch (IOException | JsonParseException e) {
LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
}
}
return config;
}
static void saveConfig(Config config) {
try {
Files.write(CONFIG_PATH, config.toJson().getBytes(UTF_8));
} catch (IOException ex) {
LOG.log(Level.SEVERE, "Failed to save config", ex);
}
}
}
@@ -17,7 +17,7 @@
*/
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.Launcher;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Profiles {
@@ -30,9 +30,9 @@ public final class Profiles {
public static String getProfileDisplayName(Profile profile) {
switch (profile.getName()) {
case Profiles.DEFAULT_PROFILE:
return Launcher.i18n("profile.default");
return i18n("profile.default");
case Profiles.HOME_PROFILE:
return Launcher.i18n("profile.home");
return i18n("profile.home");
default:
return profile.getName();
}
@@ -17,21 +17,11 @@
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import javafx.scene.text.Font;
import org.hildan.fxgson.creators.ObservableListCreator;
import org.hildan.fxgson.creators.ObservableMapCreator;
import org.hildan.fxgson.creators.ObservableSetCreator;
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
@@ -41,9 +31,8 @@ import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.event.*;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.i18n.Locales;
import java.io.File;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
@@ -59,21 +48,6 @@ import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.Logging.LOG;
public class Settings {
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
.setPrettyPrinting()
.create();
public static final String SETTINGS_FILE_NAME = "hmcl.json";
public static final File SETTINGS_FILE = new File(SETTINGS_FILE_NAME).getAbsoluteFile();
public static final Config SETTINGS = initSettings();
public static final Settings INSTANCE = new Settings();
@@ -82,11 +56,12 @@ public class Settings {
private final boolean firstLaunch;
private Settings() {
firstLaunch = SETTINGS.firstLaunch.get();
firstLaunch = ConfigHolder.CONFIG.firstLaunch.get();
ConfigHolder.CONFIG.firstLaunch.set(false);
loadProxy();
for (Iterator<Map<Object, Object>> iterator = SETTINGS.accounts.iterator(); iterator.hasNext();) {
for (Iterator<Map<Object, Object>> iterator = ConfigHolder.CONFIG.accounts.iterator(); iterator.hasNext();) {
Map<Object, Object> settings = iterator.next();
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(tryCast(settings.get("type"), String.class).orElse(""));
if (factory == null) {
@@ -107,7 +82,9 @@ public class Settings {
accounts.put(Accounts.getAccountId(account), account);
}
SETTINGS.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
ConfigHolder.CONFIG.authlibInjectorServers.addListener(onInvalidating(this::removeDanglingAuthlibInjectorAccounts));
this.selectedAccount.set(accounts.get(ConfigHolder.CONFIG.selectedAccount.get()));
checkProfileMap();
@@ -122,51 +99,26 @@ public class Settings {
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
}
private static Config initSettings() {
Config c = new Config();
if (SETTINGS_FILE.exists())
try {
String str = FileUtils.readText(SETTINGS_FILE);
if (StringUtils.isBlank(str))
Logging.LOG.finer("Settings file is empty, use the default settings.");
else {
Config d = GSON.fromJson(str, Config.class);
if (d != null)
c = d;
}
Logging.LOG.finest("Initialized settings.");
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
}
return c;
}
public void save() {
try {
SETTINGS.accounts.clear();
SETTINGS.firstLaunch.set(false);
for (Account account : accounts.values()) {
Map<Object, Object> storage = account.toStorage();
storage.put("type", Accounts.getAccountType(account));
SETTINGS.accounts.add(storage);
}
FileUtils.writeText(SETTINGS_FILE, GSON.toJson(SETTINGS));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to save config", ex);
ConfigHolder.CONFIG.accounts.clear();
for (Account account : accounts.values()) {
Map<Object, Object> storage = account.toStorage();
storage.put("type", Accounts.getAccountType(account));
ConfigHolder.CONFIG.accounts.add(storage);
}
ConfigHolder.saveConfig(ConfigHolder.CONFIG);
}
public boolean isFirstLaunch() {
return firstLaunch;
}
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.commonDirectory.get()) {
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", ConfigHolder.CONFIG.commonDirectory.get()) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.commonDirectory.set(get());
ConfigHolder.CONFIG.commonDirectory.set(get());
save();
}
};
@@ -183,7 +135,7 @@ public class Settings {
this.commonPath.set(commonPath);
}
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.localization.get());
private Locales.SupportedLocale locale = Locales.getLocaleByName(ConfigHolder.CONFIG.localization.get());
public Locales.SupportedLocale getLocale() {
return locale;
@@ -191,7 +143,7 @@ public class Settings {
public void setLocale(Locales.SupportedLocale locale) {
this.locale = locale;
SETTINGS.localization.set(Locales.getNameByLocale(locale));
ConfigHolder.CONFIG.localization.set(Locales.getNameByLocale(locale));
save();
}
@@ -201,7 +153,7 @@ public class Settings {
return proxy;
}
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.proxyType.get());
private Proxy.Type proxyType = Proxies.getProxyType(ConfigHolder.CONFIG.proxyType.get());
public Proxy.Type getProxyType() {
return proxyType;
@@ -209,62 +161,62 @@ public class Settings {
public void setProxyType(Proxy.Type proxyType) {
this.proxyType = proxyType;
SETTINGS.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
ConfigHolder.CONFIG.proxyType.set(Proxies.PROXIES.indexOf(proxyType));
save();
loadProxy();
}
public String getProxyHost() {
return SETTINGS.proxyHost.get();
return ConfigHolder.CONFIG.proxyHost.get();
}
public void setProxyHost(String proxyHost) {
SETTINGS.proxyHost.set(proxyHost);
ConfigHolder.CONFIG.proxyHost.set(proxyHost);
save();
}
public String getProxyPort() {
return SETTINGS.proxyPort.get();
return ConfigHolder.CONFIG.proxyPort.get();
}
public void setProxyPort(String proxyPort) {
SETTINGS.proxyPort.set(proxyPort);
ConfigHolder.CONFIG.proxyPort.set(proxyPort);
save();
}
public String getProxyUser() {
return SETTINGS.proxyUser.get();
return ConfigHolder.CONFIG.proxyUser.get();
}
public void setProxyUser(String proxyUser) {
SETTINGS.proxyUser.set(proxyUser);
ConfigHolder.CONFIG.proxyUser.set(proxyUser);
save();
}
public String getProxyPass() {
return SETTINGS.proxyPass.get();
return ConfigHolder.CONFIG.proxyPass.get();
}
public void setProxyPass(String proxyPass) {
SETTINGS.proxyPass.set(proxyPass);
ConfigHolder.CONFIG.proxyPass.set(proxyPass);
save();
}
public boolean hasProxy() {
return SETTINGS.hasProxy.get();
return ConfigHolder.CONFIG.hasProxy.get();
}
public void setHasProxy(boolean hasProxy) {
SETTINGS.hasProxy.set(hasProxy);
ConfigHolder.CONFIG.hasProxy.set(hasProxy);
save();
}
public boolean hasProxyAuth() {
return SETTINGS.hasProxyAuth.get();
return ConfigHolder.CONFIG.hasProxyAuth.get();
}
public void setHasProxyAuth(boolean hasProxyAuth) {
SETTINGS.hasProxyAuth.set(hasProxyAuth);
ConfigHolder.CONFIG.hasProxyAuth.set(hasProxyAuth);
save();
}
@@ -295,21 +247,21 @@ public class Settings {
}
public Font getFont() {
return Font.font(SETTINGS.fontFamily.get(), SETTINGS.fontSize.get());
return Font.font(ConfigHolder.CONFIG.fontFamily.get(), ConfigHolder.CONFIG.fontSize.get());
}
public void setFont(Font font) {
SETTINGS.fontFamily.set(font.getFamily());
SETTINGS.fontSize.set(font.getSize());
ConfigHolder.CONFIG.fontFamily.set(font.getFamily());
ConfigHolder.CONFIG.fontSize.set(font.getSize());
save();
}
public int getLogLines() {
return Math.max(SETTINGS.logLines.get(), 100);
return Math.max(ConfigHolder.CONFIG.logLines.get(), 100);
}
public void setLogLines(int logLines) {
SETTINGS.logLines.set(logLines);
ConfigHolder.CONFIG.logLines.set(logLines);
save();
}
@@ -325,7 +277,7 @@ public class Settings {
private void removeDanglingAuthlibInjectorAccounts() {
accounts.values().stream()
.filter(AuthlibInjectorAccount.class::isInstance)
.filter(it -> !SETTINGS.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
.filter(it -> !ConfigHolder.CONFIG.authlibInjectorServers.contains(((AuthlibInjectorAccount) it).getServer()))
.collect(toList())
.forEach(this::deleteAccount);
}
@@ -335,14 +287,14 @@ public class Settings {
****************************************/
public DownloadProvider getDownloadProvider() {
return DownloadProviders.getDownloadProvider(SETTINGS.downloadType.get());
return DownloadProviders.getDownloadProvider(ConfigHolder.CONFIG.downloadType.get());
}
public void setDownloadProvider(DownloadProvider downloadProvider) {
int index = DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(downloadProvider);
if (index == -1)
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
SETTINGS.downloadType.set(index);
ConfigHolder.CONFIG.downloadType.set(index);
save();
}
@@ -350,7 +302,7 @@ public class Settings {
* ACCOUNTS *
****************************************/
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(SETTINGS.selectedAccount.get())) {
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", null) {
@Override
public Account get() {
Account a = super.get();
@@ -372,7 +324,7 @@ public class Settings {
public void invalidated() {
super.invalidated();
SETTINGS.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
ConfigHolder.CONFIG.selectedAccount.set(getValue() == null ? "" : Accounts.getAccountId(getValue()));
save();
}
};
@@ -422,12 +374,12 @@ public class Settings {
* BACKGROUND *
****************************************/
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", SETTINGS.backgroundImage.get()) {
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", ConfigHolder.CONFIG.backgroundImage.get()) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.backgroundImage.set(get());
ConfigHolder.CONFIG.backgroundImage.set(get());
save();
}
};
@@ -444,12 +396,12 @@ public class Settings {
this.backgroundImage.set(backgroundImage);
}
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.backgroundImageType.get())) {
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(ConfigHolder.CONFIG.backgroundImageType.get())) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.backgroundImageType.set(get().ordinal());
ConfigHolder.CONFIG.backgroundImageType.set(get().ordinal());
save();
}
};
@@ -470,12 +422,12 @@ public class Settings {
* THEME *
****************************************/
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.theme.get()).orElse(Theme.BLUE)) {
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(ConfigHolder.CONFIG.theme.get()).orElse(Theme.BLUE)) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.theme.set(get().getName().toLowerCase());
ConfigHolder.CONFIG.theme.set(get().getName().toLowerCase());
save();
}
};
@@ -499,19 +451,19 @@ public class Settings {
public Profile getSelectedProfile() {
checkProfileMap();
if (!hasProfile(SETTINGS.selectedProfile.get())) {
if (!hasProfile(ConfigHolder.CONFIG.selectedProfile.get())) {
getProfileMap().keySet().stream().findFirst().ifPresent(selectedProfile -> {
SETTINGS.selectedProfile.set(selectedProfile);
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile);
save();
});
Schedulers.computation().schedule(this::onProfileChanged);
}
return getProfile(SETTINGS.selectedProfile.get());
return getProfile(ConfigHolder.CONFIG.selectedProfile.get());
}
public void setSelectedProfile(Profile selectedProfile) {
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.selectedProfile.get())) {
SETTINGS.selectedProfile.set(selectedProfile.getName());
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), ConfigHolder.CONFIG.selectedProfile.get())) {
ConfigHolder.CONFIG.selectedProfile.set(selectedProfile.getName());
save();
Schedulers.computation().schedule(this::onProfileChanged);
}
@@ -529,7 +481,7 @@ public class Settings {
}
public Map<String, Profile> getProfileMap() {
return SETTINGS.configurations;
return ConfigHolder.CONFIG.configurations;
}
public Collection<Profile> getProfiles() {
@@ -588,6 +540,6 @@ public class Settings {
}
public Config getRawConfig() {
return SETTINGS.clone();
return ConfigHolder.CONFIG.clone();
}
}
@@ -21,13 +21,13 @@ import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXProgressBar;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import java.util.function.Consumer;
@@ -35,7 +35,6 @@ public class AccountLoginPane extends StackPane {
private final Account oldAccount;
private final Consumer<AuthInfo> success;
private final Runnable failed;
private final Consumer<Region> closeConsumer;
@FXML
private Label lblUsername;
@@ -43,11 +42,10 @@ public class AccountLoginPane extends StackPane {
@FXML private Label lblCreationWarning;
@FXML private JFXProgressBar progressBar;
public AccountLoginPane(Account oldAccount, Consumer<Region> closeConsumer, Consumer<AuthInfo> success, Runnable failed) {
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
this.oldAccount = oldAccount;
this.success = success;
this.failed = failed;
this.closeConsumer = closeConsumer;
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
@@ -70,9 +68,9 @@ public class AccountLoginPane extends StackPane {
Object account = variable.get("login");
if (account instanceof AuthInfo) {
success.accept(((AuthInfo) account));
closeConsumer.accept(this);
fireEvent(new DialogCloseEvent());
} else if (account instanceof NoSelectedCharacterException) {
closeConsumer.accept(this);
fireEvent(new DialogCloseEvent());
} else if (account instanceof Exception) {
lblCreationWarning.setText(AddAccountPane.accountException((Exception) account));
}
@@ -84,6 +82,6 @@ public class AccountLoginPane extends StackPane {
@FXML
private void onCancel() {
failed.run();
closeConsumer.accept(this);
fireEvent(new DialogCloseEvent());
}
}
@@ -28,9 +28,10 @@ import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.AccountHelper;
@@ -40,6 +41,9 @@ import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import static org.jackhuang.hmcl.ui.FXUtils.installTooltip;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Optional;
public class AccountPage extends StackPane implements DecoratorPage {
@@ -73,22 +77,20 @@ public class AccountPage extends StackPane implements DecoratorPage {
this.account = account;
this.item = item;
title = new SimpleStringProperty(this, "title", Launcher.i18n("account") + " - " + account.getCharacter());
title = new SimpleStringProperty(this, "title", i18n("account") + " - " + account.getCharacter());
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
FXUtils.setLimitWidth(this, 300);
if (account instanceof AuthlibInjectorAccount) {
lblServer.setText(((AuthlibInjectorAccount) account).getServer().getName());
FXUtils.setLimitHeight(this, 182);
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
lblServer.setText(server.getName());
installTooltip(lblServer, server.getUrl());
} else {
componentList.removeChildren(paneServer);
if (account instanceof OfflineAccount) {
componentList.removeChildren(paneEmail);
FXUtils.setLimitHeight(this, 110);
} else
FXUtils.setLimitHeight(this, 145);
}
}
btnDelete.setGraphic(SVG.delete(Theme.blackFillBinding(), 15, 15));
@@ -19,18 +19,19 @@ package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.*;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
@@ -40,23 +41,25 @@ import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.AccountHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.IconedItem;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import static org.jackhuang.hmcl.ui.FXUtils.jfxListCellFactory;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.function.Consumer;
import java.util.logging.Level;
public class AddAccountPane extends StackPane {
@@ -72,47 +75,49 @@ public class AddAccountPane extends StackPane {
@FXML private JFXDialogLayout layout;
@FXML private JFXButton btnAccept;
@FXML private SpinnerPane acceptPane;
private final Consumer<Region> finalization;
public AddAccountPane(Consumer<Region> finalization) {
this.finalization = finalization;
public AddAccountPane() {
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
cboServers.setItems(Settings.SETTINGS.authlibInjectorServers);
cboServers.setPromptText(Launcher.i18n("general.prompt.empty"));
Bindings.bindContent(cboServers.getItems(), ConfigHolder.CONFIG.authlibInjectorServers);
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
selectDefaultServer();
// workaround: otherwise the combox will be black
if (!cboServers.getItems().isEmpty())
cboServers.getSelectionModel().select(0);
cboType.getItems().setAll(Launcher.i18n("account.methods.offline"), Launcher.i18n("account.methods.yggdrasil"), Launcher.i18n("account.methods.authlib_injector"));
cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
txtPassword.setVisible(newValue.intValue() != 0);
lblPassword.setVisible(newValue.intValue() != 0);
cboServers.setVisible(newValue.intValue() == 2);
linkManageInjectorServers.setVisible(newValue.intValue() == 2);
lblInjectorServer.setVisible(newValue.intValue() == 2);
validateAcceptButton();
});
cboType.getItems().setAll(i18n("account.methods.offline"), i18n("account.methods.yggdrasil"), i18n("account.methods.authlib_injector"));
cboType.getSelectionModel().select(0);
txtPassword.setOnAction(e -> onCreationAccept());
txtUsername.setOnAction(e -> onCreationAccept());
txtUsername.getValidators().add(new Validator(Launcher.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
ReadOnlyIntegerProperty loginTypeIdProperty = cboType.getSelectionModel().selectedIndexProperty();
txtUsername.textProperty().addListener(it -> validateAcceptButton());
txtPassword.textProperty().addListener(it -> validateAcceptButton());
txtPassword.visibleProperty().bind(loginTypeIdProperty.isNotEqualTo(0));
lblPassword.visibleProperty().bind(txtPassword.visibleProperty());
cboServers.visibleProperty().bind(loginTypeIdProperty.isEqualTo(2));
lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty());
linkManageInjectorServers.visibleProperty().bind(cboServers.visibleProperty());
txtUsername.getValidators().add(new Validator(i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
btnAccept.disableProperty().bind(Bindings.createBooleanBinding(
() -> !txtUsername.validate() || (loginTypeIdProperty.get() != 0 && !txtPassword.validate()),
txtUsername.textProperty(), txtPassword.textProperty(), loginTypeIdProperty));
}
private void validateAcceptButton() {
btnAccept.setDisable(!txtUsername.validate() || (cboType.getSelectionModel().getSelectedIndex() != 0 && !txtPassword.validate()));
/**
* Selects the first server if no server is selected.
*/
private void selectDefaultServer() {
if (!cboServers.getItems().isEmpty() && cboServers.getSelectionModel().isEmpty()) {
cboServers.getSelectionModel().select(0);
}
}
@FXML
private void onCreationAccept() {
if (btnAccept.isDisabled())
return;
String username = txtUsername.getText();
String password = txtPassword.getText();
Object addtionalData;
@@ -134,7 +139,7 @@ public class AddAccountPane extends StackPane {
if (server.isPresent()) {
addtionalData = server.get();
} else {
lblCreationWarning.setText(Launcher.i18n("account.failed.no_selected_server"));
lblCreationWarning.setText(i18n("account.failed.no_selected_server"));
return;
}
break;
@@ -144,41 +149,35 @@ public class AddAccountPane extends StackPane {
acceptPane.showSpinner();
lblCreationWarning.setText("");
setDisable(true);
Task.ofResult("create_account", () -> factory.create(new Selector(), username, password, addtionalData, Settings.INSTANCE.getProxy()))
.finalized(Schedulers.javafx(), variables -> {
Settings.INSTANCE.addAccount(variables.get("create_account"));
acceptPane.hideSpinner();
finalization.accept(this);
fireEvent(new DialogCloseEvent());
}, exception -> {
if (exception instanceof NoSelectedCharacterException) {
finalization.accept(this);
fireEvent(new DialogCloseEvent());
} else {
lblCreationWarning.setText(accountException(exception));
}
setDisable(false);
acceptPane.hideSpinner();
}).start();
}
@FXML
private void onCreationCancel() {
finalization.accept(this);
fireEvent(new DialogCloseEvent());
}
@FXML
private void onManageInjecterServers() {
finalization.accept(this);
fireEvent(new DialogCloseEvent());
Controllers.navigate(Controllers.getServersPage());
}
private void showSelector(Node node) {
getChildren().setAll(node);
}
private void closeSelector() {
getChildren().setAll(layout);
}
private class Selector extends BorderPane implements CharacterSelector {
private final AdvancedListBox listBox = new AdvancedListBox();
private final JFXButton cancel = new JFXButton();
@@ -189,11 +188,11 @@ public class AddAccountPane extends StackPane {
{
setStyle("-fx-padding: 8px;");
cancel.setText(Launcher.i18n("button.cancel"));
cancel.setText(i18n("button.cancel"));
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
cancel.setOnMouseClicked(e -> latch.countDown());
listBox.startCategory(Launcher.i18n("account.choose"));
listBox.startCategory(i18n("account.choose"));
setCenter(listBox);
@@ -235,7 +234,7 @@ public class AddAccountPane extends StackPane {
listBox.add(accountItem);
}
JFXUtilities.runInFX(() -> showSelector(this));
JFXUtilities.runInFX(() -> Controllers.dialog(this));
try {
latch.await();
@@ -243,41 +242,41 @@ public class AddAccountPane extends StackPane {
if (selectedProfile == null)
throw new NoSelectedCharacterException(account);
JFXUtilities.runInFX(AddAccountPane.this::closeSelector);
return selectedProfile;
} catch (InterruptedException ignore) {
throw new NoSelectedCharacterException(account);
} finally {
JFXUtilities.runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent()));
}
}
}
public static String accountException(Exception exception) {
if (exception instanceof NoCharacterException) {
return Launcher.i18n("account.failed.no_character");
return i18n("account.failed.no_character");
} else if (exception instanceof ServerDisconnectException) {
return Launcher.i18n("account.failed.connect_authentication_server");
return i18n("account.failed.connect_authentication_server");
} else if (exception instanceof RemoteAuthenticationException) {
RemoteAuthenticationException remoteException = (RemoteAuthenticationException) exception;
String remoteMessage = remoteException.getRemoteMessage();
if ("ForbiddenOperationException".equals(remoteException.getRemoteName()) && remoteMessage != null) {
if (remoteMessage.contains("Invalid credentials"))
return Launcher.i18n("account.failed.invalid_credentials");
return i18n("account.failed.invalid_credentials");
else if (remoteMessage.contains("Invalid token"))
return Launcher.i18n("account.failed.invalid_token");
return i18n("account.failed.invalid_token");
else if (remoteMessage.contains("Invalid username or password"))
return Launcher.i18n("account.failed.invalid_password");
return i18n("account.failed.invalid_password");
}
return exception.getMessage();
} else {
return exception.getClass() + ": " + exception.getLocalizedMessage();
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
}
}
public static String accountType(Account account) {
if (account instanceof OfflineAccount) return Launcher.i18n("account.methods.offline");
else if (account instanceof AuthlibInjectorAccount) return Launcher.i18n("account.methods.authlib_injector");
else if (account instanceof YggdrasilAccount) return Launcher.i18n("account.methods.yggdrasil");
else throw new Error(Launcher.i18n("account.methods.no_method") + ": " + account);
if (account instanceof OfflineAccount) return i18n("account.methods.offline");
else if (account instanceof AuthlibInjectorAccount) return i18n("account.methods.authlib_injector");
else if (account instanceof YggdrasilAccount) return i18n("account.methods.yggdrasil");
else throw new Error(i18n("account.methods.no_method") + ": " + account);
}
}
@@ -0,0 +1,141 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.IOException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.NetworkUtils;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXTextField;
import javafx.beans.binding.Bindings;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
public class AddAuthlibInjectorServerPane extends StackPane {
@FXML private StackPane addServerContainer;
@FXML private Label lblServerUrl;
@FXML private Label lblServerName;
@FXML private Label lblCreationWarning;
@FXML private Label lblServerWarning;
@FXML private JFXTextField txtServerUrl;
@FXML private JFXDialogLayout addServerPane;
@FXML private JFXDialogLayout confirmServerPane;
@FXML private SpinnerPane nextPane;
@FXML private JFXButton btnAddNext;
private TransitionHandler transitionHandler;
private AuthlibInjectorServer serverBeingAdded;
public AddAuthlibInjectorServerPane(String url) {
this();
txtServerUrl.setText(url);
onAddNext();
}
public AddAuthlibInjectorServerPane() {
loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml");
transitionHandler = new TransitionHandler(addServerContainer);
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
btnAddNext.disableProperty().bind(
Bindings.createBooleanBinding(txtServerUrl::validate, txtServerUrl.textProperty()).not());
nextPane.hideSpinner();
}
private String fixInputUrl(String url) {
if (!url.endsWith("/")) {
url += "/";
}
return url;
}
private String resolveFetchExceptionMessage(Throwable exception) {
if (exception instanceof IOException) {
return i18n("account.failed.connect_injector_server");
} else {
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
}
}
@FXML
private void onAddCancel() {
fireEvent(new DialogCloseEvent());
}
@FXML
private void onAddNext() {
if (btnAddNext.isDisabled())
return;
lblCreationWarning.setText("");
String url = fixInputUrl(txtServerUrl.getText());
nextPane.showSpinner();
addServerPane.setDisable(true);
Task.of(() -> {
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
addServerPane.setDisable(false);
nextPane.hideSpinner();
if (isDependentsSucceeded) {
lblServerName.setText(serverBeingAdded.getName());
lblServerUrl.setText(serverBeingAdded.getUrl());
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else {
lblCreationWarning.setText(resolveFetchExceptionMessage(variables.<Exception>get("lastException")));
}
}).start();
}
@FXML
private void onAddPrev() {
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
}
@FXML
private void onAddFinish() {
if (!ConfigHolder.CONFIG.authlibInjectorServers.contains(serverBeingAdded)) {
ConfigHolder.CONFIG.authlibInjectorServers.add(serverBeingAdded);
}
fireEvent(new DialogCloseEvent());
}
}
@@ -1,134 +1,69 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import static java.util.stream.Collectors.toList;
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
import static org.jackhuang.hmcl.ui.FXUtils.smoothScrolling;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.NetworkUtils;
import static java.util.stream.Collectors.toList;
import java.io.IOException;
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("account.injector.server"));
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("account.injector.manage.title"));
@FXML private ScrollPane scrollPane;
@FXML private StackPane addServerContainer;
@FXML private Label lblServerUrl;
@FXML private Label lblServerName;
@FXML private Label lblCreationWarning;
@FXML private Label lblServerWarning;
@FXML private VBox listPane;
@FXML private JFXTextField txtServerUrl;
@FXML private JFXDialogLayout addServerPane;
@FXML private JFXDialogLayout confirmServerPane;
@FXML private JFXDialog dialog;
@FXML private StackPane contentPane;
@FXML private SpinnerPane nextPane;
@FXML private JFXButton btnAddNext;
private final TransitionHandler transitionHandler;
private InvalidationListener serversListener;
private AuthlibInjectorServer serverBeingAdded;
public AuthlibInjectorServersPage() {
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
smoothScrolling(scrollPane);
{
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
FXUtils.smoothScrolling(scrollPane);
transitionHandler = new TransitionHandler(addServerContainer);
getChildren().remove(dialog);
dialog.setDialogContainer(this);
txtServerUrl.textProperty().addListener((a, b, newValue) ->
btnAddNext.setDisable(!txtServerUrl.validate()));
reload();
serversListener = observable -> updateServersList();
ConfigHolder.CONFIG.authlibInjectorServers.addListener(new WeakInvalidationListener(serversListener));
updateServersList();
}
private void removeServer(AuthlibInjectorServerItem item) {
Settings.SETTINGS.authlibInjectorServers.remove(item.getServer());
reload();
}
private void reload() {
private void updateServersList() {
listPane.getChildren().setAll(
Settings.SETTINGS.authlibInjectorServers.stream()
.map(server -> new AuthlibInjectorServerItem(server, this::removeServer))
ConfigHolder.CONFIG.authlibInjectorServers.stream()
.map(server -> new AuthlibInjectorServerItem(server,
item -> ConfigHolder.CONFIG.authlibInjectorServers.remove(item.getServer())))
.collect(toList()));
if (Settings.SETTINGS.authlibInjectorServers.isEmpty()) {
onAdd();
}
}
@FXML
private void onAdd() {
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
txtServerUrl.setText("");
txtServerUrl.resetValidation();
lblCreationWarning.setText("");
addServerPane.setDisable(false);
nextPane.hideSpinner();
dialog.show();
}
@FXML
private void onAddCancel() {
dialog.close();
}
@FXML
private void onAddNext() {
String url = fixInputUrl(txtServerUrl.getText());
nextPane.showSpinner();
addServerPane.setDisable(true);
Task.of(() -> {
serverBeingAdded = AuthlibInjectorServer.fetchServerInfo(url);
}).finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
nextPane.hideSpinner();
addServerPane.setDisable(false);
if (isDependentsSucceeded) {
lblServerName.setText(serverBeingAdded.getName());
lblServerUrl.setText(serverBeingAdded.getUrl());
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else {
lblCreationWarning.setText(resolveFetchExceptionMessage(variables.<Exception>get("lastException")));
}
}).start();
}
@FXML
private void onAddPrev() {
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
}
@FXML
private void onAddFinish() {
if (!Settings.INSTANCE.SETTINGS.authlibInjectorServers.contains(serverBeingAdded)) {
Settings.INSTANCE.SETTINGS.authlibInjectorServers.add(serverBeingAdded);
}
reload();
dialog.close();
Controllers.dialog(new AddAuthlibInjectorServerPane());
}
public String getTitle() {
@@ -143,19 +78,4 @@ public class AuthlibInjectorServersPage extends StackPane implements DecoratorPa
public void setTitle(String title) {
this.title.set(title);
}
private String fixInputUrl(String url) {
if (!url.endsWith("/")) {
url += "/";
}
return url;
}
private String resolveFetchExceptionMessage(Throwable exception) {
if (exception instanceof IOException) {
return Launcher.i18n("account.failed.connect_injector_server");
} else {
return exception.getClass() + ": " + exception.getLocalizedMessage();
}
}
}
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
@@ -132,15 +133,15 @@ public final class Controllers {
}
public static void dialog(String text, String title, int type, Runnable onAccept) {
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, type, onAccept));
dialog(new MessageDialogPane(text, title, type, onAccept));
}
public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
dialog(new MessageDialogPane(text, title, Controllers::closeDialog, onAccept, onCancel));
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
}
public static void inputDialog(String text, Consumer<String> onResult) {
dialog(new InputDialogPane(text, Controllers::closeDialog, onResult));
dialog(new InputDialogPane(text, onResult));
}
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
@@ -156,6 +157,9 @@ public final class Controllers {
return pane;
}
/**
* Use {@link DialogCloseEvent}
*/
public static void closeDialog(Region content) {
if (stage == null) // shut down
return;
@@ -27,6 +27,9 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import org.jackhuang.hmcl.Launcher;
/**
@@ -37,9 +40,9 @@ public class CrashWindow extends Stage {
public CrashWindow(String text) {
Label lblCrash = new Label();
if (Launcher.UPDATE_CHECKER.isOutOfDate())
lblCrash.setText(Launcher.i18n("launcher.crash_out_dated"));
lblCrash.setText(i18n("launcher.crash_out_dated"));
else
lblCrash.setText(Launcher.i18n("launcher.crash"));
lblCrash.setText(i18n("launcher.crash"));
lblCrash.setWrapText(true);
TextArea textArea = new TextArea();
@@ -47,7 +50,7 @@ public class CrashWindow extends Stage {
textArea.setEditable(false);
Button btnContact = new Button();
btnContact.setText(Launcher.i18n("launcher.contact"));
btnContact.setText(i18n("launcher.contact"));
btnContact.setOnMouseClicked(event -> FXUtils.openLink(Launcher.CONTACT));
HBox box = new HBox();
box.setStyle("-fx-padding: 8px;");
@@ -65,7 +68,7 @@ public class CrashWindow extends Stage {
Scene scene = new Scene(pane, 800, 480);
setScene(scene);
getIcons().add(new Image("/assets/img/icon.png"));
setTitle(Launcher.i18n("message.error"));
setTitle(i18n("message.error"));
setOnCloseRequest(e -> System.exit(1));
}
@@ -32,6 +32,7 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
@@ -43,6 +44,7 @@ import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.DragEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
@@ -52,23 +54,24 @@ import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDnD;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
import org.jackhuang.hmcl.setting.Locales;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.StackContainerPane;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
import org.jackhuang.hmcl.ui.wizard.*;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.util.Locale;
import java.util.Optional;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -83,6 +86,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
private static final SVGGlyph close = Lang.apply(new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE),
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
private static final String PROPERTY_DIALOG_CLOSE_HANDLER = Decorator.class.getName() + ".dialog.closeListener";
private final ObjectProperty<Runnable> onCloseButtonAction;
private final BooleanProperty customMaximize = new SimpleBooleanProperty(false);
@@ -91,13 +96,14 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
private final boolean max, min;
private final WizardController wizardController = new WizardController(this);
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
private final JFXDialog dialog;
private double xOffset, yOffset, newX, newY, initX, initY;
private boolean allowMove, isDragging, maximized;
private BoundingBox originalBox, maximizedBox;
private final TransitionHandler animationHandler;
private final StackContainerPane dialogPane = new StackContainerPane();
private JFXDialog dialog;
private StackContainerPane dialogPane;
@FXML
private StackPane contentPlaceHolder;
@@ -176,12 +182,6 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
btnMax.fire();
});
dialog = new JFXDialog();
dialog.setOverlayClose(false);
drawerWrapper.getChildren().add(0, dialog);
dialog.setDialogContainer(drawerWrapper);
dialog.setContent(dialogPane);
welcomeView.setCursor(Cursor.HAND);
welcomeView.setOnMouseClicked(e -> {
Timeline nowAnimation = new Timeline();
@@ -211,6 +211,8 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
animationHandler = new TransitionHandler(contentPlaceHolder);
loadBackground();
setupAuthlibInjectorDnD();
}
private void loadBackground() {
@@ -565,20 +567,37 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
public void showDialog(Node node) {
FXUtils.checkFxUserThread();
if (dialogPane.isEmpty())
if (dialog == null) {
dialog = new JFXDialog();
dialogPane = new StackContainerPane();
dialog.setContent(dialogPane);
dialog.setDialogContainer(drawerWrapper);
dialog.setOverlayClose(false);
dialog.show();
}
dialogPane.push(node);
EventHandler<DialogCloseEvent> handler = event -> closeDialog(node);
node.getProperties().put(PROPERTY_DIALOG_CLOSE_HANDLER, handler);
node.addEventHandler(DialogCloseEvent.CLOSE, handler);
}
@SuppressWarnings("unchecked")
public void closeDialog(Node node) {
FXUtils.checkFxUserThread();
Optional.ofNullable(node.getProperties().get(PROPERTY_DIALOG_CLOSE_HANDLER))
.ifPresent(handler -> node.removeEventHandler(DialogCloseEvent.CLOSE, (EventHandler<DialogCloseEvent>) handler));
dialogPane.pop(node);
if (dialogPane.getChildren().isEmpty())
Platform.runLater(() -> {
if (dialogPane.getChildren().isEmpty())
dialog.close();
});
if (dialogPane.getChildren().isEmpty()) {
dialog.close();
dialog = null;
dialogPane = null;
}
}
public void startWizard(WizardProvider wizardProvider) {
@@ -672,4 +691,10 @@ public final class Decorator extends StackPane implements TaskExecutorDialogWiza
public AdvancedListBox getLeftPane() {
return leftPane;
}
private void setupAuthlibInjectorDnD() {
addEventFilter(DragEvent.DRAG_OVER, AuthlibInjectorDnD.dragOverHandler());
addEventFilter(DragEvent.DRAG_DROPPED, AuthlibInjectorDnD.dragDroppedHandler(
url -> Controllers.dialog(new AddAuthlibInjectorServerPane(url))));
}
}
@@ -34,7 +34,7 @@ public final class DialogController {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
JFXUtilities.runInFX(() -> {
AccountLoginPane pane = new AccountLoginPane(account, Controllers::closeDialog, it -> {
AccountLoginPane pane = new AccountLoginPane(account, it -> {
res.set(it);
latch.countDown();
}, latch::countDown);
@@ -45,8 +45,8 @@ import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.i18n.I18n;
import java.io.File;
import java.io.IOException;
@@ -203,7 +203,7 @@ public final class FXUtils {
}
public static void loadFXML(Node node, String absolutePath) {
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), Launcher.RESOURCE_BUNDLE);
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), I18n.getResourceBundle());
loader.setRoot(node);
loader.setController(node);
Lang.invoke((ExceptionalSupplier<Object, IOException>) loader::load);
@@ -20,7 +20,7 @@ package org.jackhuang.hmcl.ui;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.game.GameVersion;
@@ -30,6 +30,7 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.LinkedList;
import java.util.Optional;
@@ -90,7 +91,7 @@ public class InstallerController {
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
if (!gameVersion.isPresent())
Controllers.dialog(Launcher.i18n("version.cannot_read"));
Controllers.dialog(i18n("version.cannot_read"));
else
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine));
}
@@ -21,7 +21,8 @@ import com.jfoenix.effects.JFXDepthManager;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.Launcher;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.function.Consumer;
@@ -44,7 +45,7 @@ public class InstallerItem extends BorderPane {
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
lblInstallerArtifact.setText(artifact);
lblInstallerVersion.setText(Launcher.i18n("archive.version") + ": " + version);
lblInstallerVersion.setText(i18n("archive.version") + ": " + version);
}
@FXML
@@ -28,12 +28,11 @@ import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.download.VersionList;
import org.jackhuang.hmcl.event.*;
import org.jackhuang.hmcl.game.AccountHelper;
import org.jackhuang.hmcl.game.HMCLGameRepository;
@@ -46,9 +45,11 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.ClassTitle;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.IconedItem;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.util.Lang;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.util.HashMap;
@@ -61,27 +62,27 @@ public final class LeftPaneController {
private final VBox profilePane = new VBox();
private final VBox accountPane = new VBox();
private final IconedItem launcherSettingsItem;
private final VersionListItem missingAccountItem = new VersionListItem(Launcher.i18n("account.missing"), Launcher.i18n("message.unknown"));
private final VersionListItem missingAccountItem = new VersionListItem(i18n("account.missing"), i18n("message.unknown"));
private final HashMap<Account, VersionListItem> items = new HashMap<>();
public LeftPaneController(AdvancedListBox leftPane) {
this.leftPane = leftPane;
this.launcherSettingsItem = Lang.apply(new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), Launcher.i18n("settings.launcher")), iconedItem -> {
this.launcherSettingsItem = Lang.apply(new IconedItem(SVG.gear(Theme.blackFillBinding(), 20, 20), i18n("settings.launcher")), iconedItem -> {
iconedItem.prefWidthProperty().bind(leftPane.widthProperty());
iconedItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
});
leftPane
.add(new ClassTitle(Launcher.i18n("account").toUpperCase(), Lang.apply(new JFXButton(), button -> {
.add(new ClassTitle(i18n("account").toUpperCase(), Lang.apply(new JFXButton(), button -> {
button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10));
button.getStyleClass().add("toggle-icon-tiny");
button.setOnMouseClicked(e -> addNewAccount());
})))
.add(accountPane)
.startCategory(Launcher.i18n("launcher").toUpperCase())
.startCategory(i18n("launcher").toUpperCase())
.add(launcherSettingsItem)
.add(new ClassTitle(Launcher.i18n("profile.title").toUpperCase(), Lang.apply(new JFXButton(), button -> {
.add(new ClassTitle(i18n("profile.title").toUpperCase(), Lang.apply(new JFXButton(), button -> {
button.setGraphic(SVG.plus(Theme.blackFillBinding(), 10, 10));
button.getStyleClass().add("toggle-icon-tiny");
button.setOnMouseClicked(e ->
@@ -100,7 +101,7 @@ public final class LeftPaneController {
}
private void addNewAccount() {
Controllers.dialog(new AddAccountPane(Controllers::closeDialog));
Controllers.dialog(new AddAccountPane());
}
private void onSelectedAccountChanged(Account newAccount) {
@@ -122,7 +123,7 @@ public final class LeftPaneController {
if (node instanceof RipplerContainer && node.getProperties().get("profile") instanceof String) {
boolean current = Objects.equals(node.getProperties().get("profile"), profile.getName());
((RipplerContainer) node).setSelected(current);
((VersionListItem) ((RipplerContainer) node).getContainer()).setGameVersion(current ? Launcher.i18n("profile.selected") : "");
((VersionListItem) ((RipplerContainer) node).getContainer()).setGameVersion(current ? i18n("profile.selected") : "");
}
}
});
@@ -143,9 +144,9 @@ public final class LeftPaneController {
}
private static String accountType(Account account) {
if (account instanceof OfflineAccount) return Launcher.i18n("account.methods.offline");
if (account instanceof OfflineAccount) return i18n("account.methods.offline");
else if (account instanceof YggdrasilAccount) return account.getUsername();
else throw new Error(Launcher.i18n("account.methods.no_method") + ": " + account);
else throw new Error(i18n("account.methods.no_method") + ": " + account);
}
private void onAccountAdd(AccountAddedEvent event) {
@@ -207,7 +208,7 @@ public final class LeftPaneController {
}
public void showUpdate() {
launcherSettingsItem.setText(Launcher.i18n("update.found"));
launcherSettingsItem.setText(i18n("update.found"));
launcherSettingsItem.setTextFill(Color.RED);
}
@@ -228,10 +229,10 @@ public final class LeftPaneController {
Modpack modpack = ModpackHelper.readModpackManifest(modpackFile);
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.with(Task.of(Schedulers.javafx(), () -> {
Controllers.closeDialog(region.get());
region.get().fireEvent(new DialogCloseEvent());
checkAccount();
})).executor();
region.set(Controllers.taskDialog(executor, Launcher.i18n("modpack.installing"), ""));
region.set(Controllers.taskDialog(executor, i18n("modpack.installing"), ""));
executor.start();
showNewAccount = false;
} catch (UnsupportedModpackException ignore) {
@@ -30,7 +30,7 @@ import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.game.LauncherHelper;
@@ -43,6 +43,8 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.concurrent.CountDownLatch;
/**
@@ -63,7 +65,7 @@ public final class LogWindow extends Stage {
public LogWindow() {
setScene(new Scene(impl, 800, 480));
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets());
setTitle(Launcher.i18n("logwindow.title"));
setTitle(i18n("logwindow.title"));
getIcons().add(new Image("/assets/img/icon.png"));
}
@@ -29,7 +29,7 @@ import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.ProfileChangedEvent;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
@@ -42,12 +42,12 @@ import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.OperatingSystem;
import java.io.File;
import java.io.IOException;
import java.util.List;
@@ -57,10 +57,11 @@ import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.StringUtils.removePrefix;
import static org.jackhuang.hmcl.util.StringUtils.removeSuffix;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class MainPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("main_page"));
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("main_page"));
private Profile profile;
@@ -95,10 +96,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
this.profile = event.getProfile();
});
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), Launcher.i18n("install")));
FXUtils.installTooltip(btnAdd, Launcher.i18n("install"));
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), i18n("install")));
FXUtils.installTooltip(btnAdd, i18n("install"));
btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersionsAsync().start());
FXUtils.installTooltip(btnRefresh, Launcher.i18n("button.refresh"));
FXUtils.installTooltip(btnRefresh, i18n("button.refresh"));
}
private String modifyVersion(String gameVersion, String version) {
@@ -117,13 +118,13 @@ public final class MainPage extends StackPane implements DecoratorPage {
StringBuilder libraries = new StringBuilder();
for (Library library : version.getLibraries()) {
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
libraries.append(Launcher.i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n");
libraries.append(i18n("install.installer.forge")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)forge", ""))).append("\n");
}
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
libraries.append(Launcher.i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n");
libraries.append(i18n("install.installer.liteloader")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)liteloader", ""))).append("\n");
}
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
libraries.append(Launcher.i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
libraries.append(i18n("install.installer.optifine")).append(": ").append(modifyVersion(game, library.getVersion().replaceAll("(?i)optifine", ""))).append("\n");
}
}
@@ -138,15 +139,15 @@ public final class MainPage extends StackPane implements DecoratorPage {
});
item.setOnScriptButtonClicked(e -> {
if (Settings.INSTANCE.getSelectedAccount() == null)
Controllers.dialog(Launcher.i18n("login.empty_username"));
Controllers.dialog(i18n("login.empty_username"));
else {
FileChooser chooser = new FileChooser();
if (repository.getRunDirectory(id).isDirectory())
chooser.setInitialDirectory(repository.getRunDirectory(id));
chooser.setTitle(Launcher.i18n("version.launch_script.save"));
chooser.setTitle(i18n("version.launch_script.save"));
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
? new FileChooser.ExtensionFilter(Launcher.i18n("extension.bat"), "*.bat")
: new FileChooser.ExtensionFilter(Launcher.i18n("extension.sh"), "*.sh"));
? new FileChooser.ExtensionFilter(i18n("extension.bat"), "*.bat")
: new FileChooser.ExtensionFilter(i18n("extension.sh"), "*.sh"));
File file = chooser.showSaveDialog(Controllers.getStage());
if (file != null)
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, file);
@@ -158,25 +159,25 @@ public final class MainPage extends StackPane implements DecoratorPage {
});
item.setOnUpdateButtonClicked(event -> {
FileChooser chooser = new FileChooser();
chooser.setTitle(Launcher.i18n("modpack.choose"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
chooser.setTitle(i18n("modpack.choose"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
AtomicReference<Region> region = new AtomicReference<>();
try {
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, id, ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(id)))
.then(Task.of(Schedulers.javafx(), () -> Controllers.closeDialog(region.get()))).executor();
region.set(Controllers.taskDialog(executor, Launcher.i18n("modpack.update"), ""));
.then(Task.of(Schedulers.javafx(), () -> region.get().fireEvent(new DialogCloseEvent()))).executor();
region.set(Controllers.taskDialog(executor, i18n("modpack.update"), ""));
executor.start();
} catch (UnsupportedModpackException e) {
Controllers.closeDialog(region.get());
Controllers.dialog(Launcher.i18n("modpack.unsupported"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.unsupported"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (MismatchedModpackTypeException e) {
Controllers.closeDialog(region.get());
Controllers.dialog(Launcher.i18n("modpack.mismatched_type"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.mismatched_type"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (IOException e) {
Controllers.closeDialog(region.get());
Controllers.dialog(Launcher.i18n("modpack.invalid"), Launcher.i18n("message.error"), MessageBox.ERROR_MESSAGE);
region.get().fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("modpack.invalid"), i18n("message.error"), MessageBox.ERROR_MESSAGE);
}
}
});
@@ -187,10 +188,10 @@ public final class MainPage extends StackPane implements DecoratorPage {
versionList.getStyleClass().add("option-list-view");
FXUtils.setLimitWidth(versionList, 150);
versionList.getItems().setAll(Lang.immutableListOf(
Launcher.i18n("version.manage.rename"),
Launcher.i18n("version.manage.remove"),
Launcher.i18n("modpack.export"),
Launcher.i18n("folder.game")
i18n("version.manage.rename"),
i18n("version.manage.remove"),
i18n("modpack.export"),
i18n("folder.game")
));
versionList.setOnMouseClicked(e -> {
versionPopup.hide();
@@ -214,7 +215,7 @@ public final class MainPage extends StackPane implements DecoratorPage {
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
if (Settings.INSTANCE.getSelectedAccount() == null)
Controllers.dialog(Launcher.i18n("login.empty_username"));
Controllers.dialog(i18n("login.empty_username"));
else
LauncherHelper.INSTANCE.launch(profile, Settings.INSTANCE.getSelectedAccount(), id, null);
}
@@ -26,13 +26,14 @@ import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.mod.ModInfo;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.io.IOException;
@@ -42,7 +43,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public final class ModController {
@FXML
@@ -140,8 +140,8 @@ public final class ModController {
@FXML
private void onAdd() {
FileChooser chooser = new FileChooser();
chooser.setTitle(Launcher.i18n("mods.choose_mod"));
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(Launcher.i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
chooser.setTitle(i18n("mods.choose_mod"));
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
// It's guaranteed that succeeded and failed are thread safe here.
@@ -163,10 +163,10 @@ public final class ModController {
}).with(Task.of(Schedulers.javafx(), variables -> {
List<String> prompt = new LinkedList<>();
if (!succeeded.isEmpty())
prompt.add(Launcher.i18n("mods.add.success", String.join(", ", succeeded)));
prompt.add(i18n("mods.add.success", String.join(", ", succeeded)));
if (!failed.isEmpty())
prompt.add(Launcher.i18n("mods.add.failed", String.join(", ", failed)));
Controllers.dialog(String.join("\n", prompt), Launcher.i18n("mods.add"));
prompt.add(i18n("mods.add.failed", String.join(", ", failed)));
Controllers.dialog(String.join("\n", prompt), i18n("mods.add"));
loadMods(modManager, versionId);
})).start();
}
@@ -17,14 +17,16 @@
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.mod.ModInfo;
import org.jackhuang.hmcl.setting.Theme;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.function.Consumer;
@@ -40,7 +42,9 @@ public final class ModItem extends BorderPane {
setCenter(modItem);
JFXButton btnRemove = new JFXButton();
FXUtils.installTooltip(btnRemove, Launcher.i18n("mods.remove"));
JFXUtilities.runInFX(() -> {
FXUtils.installTooltip(btnRemove, i18n("mods.remove"));
});
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
@@ -50,7 +54,7 @@ public final class ModItem extends BorderPane {
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
modItem.setTitle(info.getFileName());
modItem.setSubtitle(info.getName() + ", " + Launcher.i18n("archive.version") + ": " + info.getVersion() + ", " + Launcher.i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + Launcher.i18n("archive.author") + ": " + info.getAuthors());
modItem.setSubtitle(info.getName() + ", " + i18n("archive.version") + ": " + info.getVersion() + ", " + i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + i18n("archive.author") + ": " + info.getAuthors());
chkEnabled.setSelected(info.isActive());
chkEnabled.selectedProperty().addListener((a, b, newValue) ->
info.activeProperty().set(newValue));
@@ -23,13 +23,14 @@ import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.ui.construct.FileItem;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.StringUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.util.Optional;
@@ -54,7 +55,7 @@ public final class ProfilePage extends StackPane implements DecoratorPage {
String profileDisplayName = Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse("");
title = new SimpleStringProperty(this, "title",
profile == null ? Launcher.i18n("profile.new") : Launcher.i18n("profile") + " - " + profileDisplayName);
profile == null ? i18n("profile.new") : i18n("profile") + " - " + profileDisplayName);
location = new SimpleStringProperty(this, "location",
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
@@ -42,6 +42,9 @@ import org.jackhuang.hmcl.ui.construct.MultiFileItem;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.i18n.Locales;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.net.Proxy;
import java.util.Arrays;
@@ -49,7 +52,7 @@ import java.util.Collections;
import java.util.stream.Collectors;
public final class SettingsPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Launcher.i18n("settings.launcher"));
private final StringProperty title = new SimpleStringProperty(this, "title", i18n("settings.launcher"));
@FXML
private JFXTextField txtProxyHost;
@@ -177,12 +180,12 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
FXUtils.installTooltip(btnUpdate, Launcher.i18n("update.tooltip"));
FXUtils.installTooltip(btnUpdate, i18n("update.tooltip"));
checkUpdate();
// background
backgroundItem.loadChildren(Collections.singletonList(
backgroundItem.createChildren(Launcher.i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
));
FXUtils.bindString(backgroundItem.getTxtCustom(), Settings.INSTANCE.backgroundImageProperty());
@@ -199,8 +202,8 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
// theme
JFXColorPicker picker = new JFXColorPicker(Color.web(Settings.INSTANCE.getTheme().getColor()), null);
picker.setCustomColorText(Launcher.i18n("color.custom"));
picker.setRecentColorsText(Launcher.i18n("color.recent"));
picker.setCustomColorText(i18n("color.custom"));
picker.setRecentColorsText(i18n("color.recent"));
picker.getCustomColors().setAll(Arrays.stream(Theme.VALUES).map(Theme::getColor).map(Color::web).collect(Collectors.toList()));
picker.setOnAction(e -> {
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
@@ -214,7 +217,7 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
private void initBackgroundItemSubtitle() {
switch (Settings.INSTANCE.getBackgroundImageType()) {
case DEFAULT:
backgroundItem.setSubtitle(Launcher.i18n("launcher.background.default"));
backgroundItem.setSubtitle(i18n("launcher.background.default"));
break;
case CUSTOM:
backgroundItem.setSubtitle(Settings.INSTANCE.getBackgroundImage());
@@ -239,16 +242,16 @@ public final class SettingsPage extends StackPane implements DecoratorPage {
btnUpdate.setVisible(Launcher.UPDATE_CHECKER.isOutOfDate());
if (Launcher.UPDATE_CHECKER.isOutOfDate()) {
lblUpdateSub.setText(Launcher.i18n("update.newest_version", Launcher.UPDATE_CHECKER.getNewVersion().toString()));
lblUpdateSub.setText(i18n("update.newest_version", Launcher.UPDATE_CHECKER.getNewVersion().toString()));
lblUpdateSub.getStyleClass().setAll("update-label");
lblUpdate.setText(Launcher.i18n("update.found"));
lblUpdate.setText(i18n("update.found"));
lblUpdate.getStyleClass().setAll("update-label");
} else {
lblUpdateSub.setText(Launcher.i18n("update.latest"));
lblUpdateSub.setText(i18n("update.latest"));
lblUpdateSub.getStyleClass().setAll("subtitle-label");
lblUpdate.setText(Launcher.i18n("update"));
lblUpdate.setText(i18n("update"));
lblUpdate.getStyleClass().setAll();
}
}
@@ -1,3 +1,20 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import javafx.beans.property.SimpleStringProperty;
@@ -34,8 +34,9 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.TextAlignment;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.Theme;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Optional;
@@ -175,10 +176,10 @@ public final class VersionItem extends StackPane {
btnScript.setGraphic(SVG.script(Theme.blackFillBinding(), 15, 15));
JFXUtilities.runInFX(() -> {
FXUtils.installTooltip(btnSettings, Launcher.i18n("version.settings"));
FXUtils.installTooltip(btnUpdate, Launcher.i18n("version.update"));
FXUtils.installTooltip(btnLaunch, Launcher.i18n("version.launch"));
FXUtils.installTooltip(btnScript, Launcher.i18n("version.launch_script"));
FXUtils.installTooltip(btnSettings, i18n("version.settings"));
FXUtils.installTooltip(btnUpdate, i18n("version.update"));
FXUtils.installTooltip(btnLaunch, i18n("version.launch"));
FXUtils.installTooltip(btnScript, i18n("version.launch_script"));
});
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
@@ -26,12 +26,13 @@ import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Tab;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.FileUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
@@ -79,17 +80,17 @@ public final class VersionPage extends StackPane implements DecoratorPage {
browsePopup = new JFXPopup(browseList);
managementPopup = new JFXPopup(managementList);
FXUtils.installTooltip(btnDelete, Launcher.i18n("version.manage.remove"));
FXUtils.installTooltip(btnBrowseMenu, Launcher.i18n("settings.game.exploration"));
FXUtils.installTooltip(btnManagementMenu, Launcher.i18n("settings.game.management"));
FXUtils.installTooltip(btnExport, Launcher.i18n("modpack.export"));
FXUtils.installTooltip(btnDelete, i18n("version.manage.remove"));
FXUtils.installTooltip(btnBrowseMenu, i18n("settings.game.exploration"));
FXUtils.installTooltip(btnManagementMenu, i18n("settings.game.management"));
FXUtils.installTooltip(btnExport, i18n("modpack.export"));
}
public void load(String id, Profile profile) {
this.version = id;
this.profile = profile;
title.set(Launcher.i18n("settings.game") + " - " + id);
title.set(i18n("settings.game") + " - " + id);
versionSettingsController.loadVersionSetting(profile, id);
modController.setParentTab(tabPane);
@@ -185,7 +186,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
}
public static void deleteVersion(Profile profile, String version) {
Controllers.confirmDialog(Launcher.i18n("version.manage.remove.confirm", version), Launcher.i18n("message.confirm"), () -> {
Controllers.confirmDialog(i18n("version.manage.remove.confirm", version), i18n("message.confirm"), () -> {
if (profile.getRepository().removeVersionFromDisk(version)) {
profile.getRepository().refreshVersionsAsync().start();
Controllers.navigate(null);
@@ -194,7 +195,7 @@ public final class VersionPage extends StackPane implements DecoratorPage {
}
public static void renameVersion(Profile profile, String version) {
Controllers.inputDialog(Launcher.i18n("version.manage.rename.message"), res -> {
Controllers.inputDialog(i18n("version.manage.rename.message"), res -> {
if (profile.getRepository().renameVersion(version, res)) {
profile.getRepository().refreshVersionsAsync().start();
Controllers.navigate(null);
@@ -203,6 +204,6 @@ public final class VersionPage extends StackPane implements DecoratorPage {
}
public static void exportVersion(Profile profile, String version) {
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), Launcher.i18n("modpack.wizard"));
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), i18n("modpack.wizard"));
}
}
@@ -29,7 +29,7 @@ import javafx.scene.control.Toggle;
import javafx.scene.image.Image;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
@@ -39,6 +39,7 @@ import org.jackhuang.hmcl.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.ImagePickerItem;
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
import org.jackhuang.hmcl.util.*;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.io.IOException;
@@ -79,7 +80,7 @@ public final class VersionSettingsController {
@FXML
private void initialize() {
lblPhysicalMemory.setText(Launcher.i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB");
lblPhysicalMemory.setText(i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB");
FXUtils.smoothScrolling(scroll);
@@ -96,13 +97,13 @@ public final class VersionSettingsController {
javaItem.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe", "javaw.exe"));
gameDirItem.loadChildren(Arrays.asList(
gameDirItem.createChildren(Launcher.i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
gameDirItem.createChildren(Launcher.i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
gameDirItem.createChildren(i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
gameDirItem.createChildren(i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
));
globalItem.loadChildren(Arrays.asList(
globalItem.createChildren(Launcher.i18n("settings.type.global"), true),
globalItem.createChildren(Launcher.i18n("settings.type.special"), false)
globalItem.createChildren(i18n("settings.type.global"), true),
globalItem.createChildren(i18n("settings.type.special"), false)
));
}
@@ -115,6 +116,7 @@ public final class VersionSettingsController {
gameDirItem.setDisable(profile.getRepository().isModpack(versionId));
globalItem.setDisable(profile.getRepository().isModpack(versionId));
// unbind data fields
if (lastVersionSetting != null) {
FXUtils.unbindInt(txtWidth, lastVersionSetting.widthProperty());
FXUtils.unbindInt(txtHeight, lastVersionSetting.heightProperty());
@@ -134,6 +136,12 @@ public final class VersionSettingsController {
FXUtils.unbindEnum(cboLauncherVisibility);
}
// unbind data fields
globalItem.setToggleSelectedListener(null);
javaItem.setToggleSelectedListener(null);
gameDirItem.setToggleSelectedListener(null);
// bind new data fields
FXUtils.bindInt(txtWidth, versionSetting.widthProperty());
FXUtils.bindInt(txtHeight, versionSetting.heightProperty());
FXUtils.bindInt(txtMaxMemory, versionSetting.maxMemoryProperty());
@@ -182,7 +190,7 @@ public final class VersionSettingsController {
});
versionSetting.usesGlobalProperty().setChangedListenerAndOperate(it ->
globalItem.setSubtitle(Launcher.i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special")));
globalItem.setSubtitle(i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special")));
gameDirItem.getGroup().getToggles().stream()
.filter(it -> it.getUserData() == versionSetting.getGameDirType())
@@ -238,7 +246,7 @@ public final class VersionSettingsController {
@FXML
private void onExploreIcon() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("extension.png"), "*.png"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("extension.png"), "*.png"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
File iconFile = profile.getRepository().getVersionIcon(versionId);
@@ -18,11 +18,12 @@
package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import javafx.util.Duration;
import java.util.List;
@FunctionalInterface
public interface AnimationProducer {
void init(AnimationHandler handler);
List<KeyFrame> animate(AnimationHandler handler);
}
@@ -20,17 +20,43 @@ package org.jackhuang.hmcl.ui.animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.scene.Node;
import javafx.util.Duration;
import org.jackhuang.hmcl.util.Lang;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
public enum ContainerAnimations {
NONE(c -> Collections.emptyList()),
NONE(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c -> Collections.emptyList()),
/**
* A fade between the old and new view
*/
FADE(c ->
FADE(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
@@ -40,7 +66,12 @@ public enum ContainerAnimations {
/**
* A zoom effect
*/
ZOOM_IN(c ->
ZOOM_IN(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
@@ -52,7 +83,12 @@ public enum ContainerAnimations {
/**
* A zoom effect
*/
ZOOM_OUT(c ->
ZOOM_OUT(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}, c ->
(Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
@@ -64,7 +100,14 @@ public enum ContainerAnimations {
/**
* A swipe effect
*/
SWIPE_LEFT(c ->
SWIPE_LEFT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
@@ -75,7 +118,14 @@ public enum ContainerAnimations {
/**
* A swipe effect
*/
SWIPE_RIGHT(c ->
SWIPE_RIGHT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
@@ -85,8 +135,18 @@ public enum ContainerAnimations {
private final AnimationProducer animationProducer;
ContainerAnimations(AnimationProducer animationProducer) {
this.animationProducer = animationProducer;
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
this.animationProducer = new AnimationProducer() {
@Override
public void init(AnimationHandler handler) {
init.accept(handler);
}
@Override
public List<KeyFrame> animate(AnimationHandler handler) {
return animationProducer.apply(handler);
}
};
}
public AnimationProducer getAnimationProducer() {
@@ -19,6 +19,7 @@ package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
@@ -78,28 +79,38 @@ public final class TransitionHandler implements AnimationHandler {
updateContent(newView);
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(transition.animate(this));
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> view.getChildren().remove(previousNode)));
nowAnimation.play();
animation = nowAnimation;
transition.init(this);
// runLater or "init" will not work
Platform.runLater(() -> {
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(transition.animate(this));
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
view.setMouseTransparent(false);
view.getChildren().remove(previousNode);
}));
nowAnimation.play();
animation = nowAnimation;
});
}
private void updateContent(Node newView) {
if (view.getWidth() > 0 && view.getHeight() > 0) {
previousNode = currentNode;
if (previousNode == null)
previousNode = NULL;
previousNode = EMPTY_PANE;
} else
previousNode = NULL;
previousNode = EMPTY_PANE;
if (previousNode == newView)
previousNode = NULL;
previousNode = EMPTY_PANE;
view.setMouseTransparent(true);
currentNode = newView;
view.getChildren().setAll(previousNode, currentNode);
}
private static final StackPane NULL = new StackPane();
private final StackPane EMPTY_PANE = new StackPane();
}
@@ -15,30 +15,31 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.util;
package org.jackhuang.hmcl.ui.construct;
public class I18nException extends Exception {
private final String localizedMessage;
import org.jackhuang.hmcl.ui.Controllers;
public I18nException(String localizedMessage) {
this.localizedMessage = localizedMessage;
import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.layout.Region;
/**
* Indicates a close operation on the dialog.
*
* @author yushijinhun
* @see Controllers#dialog(Region)
*/
public class DialogCloseEvent extends Event {
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("CLOSE");
public DialogCloseEvent() {
super(CLOSE);
}
public I18nException(String localizedMessage, Throwable suppressed) {
addSuppressed(suppressed);
this.localizedMessage = localizedMessage;
public DialogCloseEvent(Object source, EventTarget target) {
super(source, target, CLOSE);
}
@Override
public String getLocalizedMessage() {
return localizedMessage;
}
public static String getStackTrace(Throwable e) {
if (e instanceof I18nException)
return e.getLocalizedMessage();
else
return StringUtils.getStackTrace(e);
}
}
@@ -26,11 +26,12 @@ import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
@@ -54,7 +55,7 @@ public class FileItem extends BorderPane {
right.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
right.getStyleClass().add("toggle-icon4");
right.setOnMouseClicked(e -> onExplore());
FXUtils.installTooltip(right, Launcher.i18n("button.edit"));
FXUtils.installTooltip(right, i18n("button.edit"));
setRight(right);
Tooltip tip = new Tooltip();
@@ -15,7 +15,9 @@ import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Launcher;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
@@ -41,7 +43,7 @@ public final class ImagePickerItem extends BorderPane {
selectButton.onMouseClickedProperty().bind(onSelectButtonClicked);
selectButton.getStyleClass().add("toggle-icon4");
FXUtils.installTooltip(selectButton, Launcher.i18n("button.edit"));
FXUtils.installTooltip(selectButton, i18n("button.edit"));
HBox hBox = new HBox();
hBox.getChildren().setAll(imageView, selectButton);
@@ -21,7 +21,6 @@ import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -38,13 +37,13 @@ public class InputDialogPane extends StackPane {
@FXML
private Label content;
public InputDialogPane(String text, Consumer<Region> closeConsumer, Consumer<String> onResult) {
public InputDialogPane(String text, Consumer<String> onResult) {
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
content.setText(text);
cancelButton.setOnMouseClicked(e -> closeConsumer.accept(this));
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
acceptButton.setOnMouseClicked(e -> {
onResult.accept(textField.getText());
closeConsumer.accept(this);
fireEvent(new DialogCloseEvent());
});
}
}
@@ -20,17 +20,19 @@ package org.jackhuang.hmcl.ui.construct;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TextInputDialog;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.FXUtils;
import javax.swing.*;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Optional;
public final class MessageBox {
private MessageBox() {
}
private static final String TITLE = Launcher.i18n("message.info");
private static final String TITLE = i18n("message.info");
/**
* User Operation: Yes
@@ -21,18 +21,16 @@ import com.jfoenix.controls.JFXButton;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Optional;
import java.util.function.Consumer;
public final class MessageDialogPane extends StackPane {
private boolean closingDialog = true;
@FXML
private JFXButton acceptButton;
@@ -47,7 +45,7 @@ public final class MessageDialogPane extends StackPane {
@FXML
private HBox actions;
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, int type, Runnable onAccept) {
public MessageDialogPane(String text, String title, int type, Runnable onAccept) {
FXUtils.loadFXML(this, "/assets/fxml/message-dialog.fxml");
if (title != null)
@@ -55,7 +53,7 @@ public final class MessageDialogPane extends StackPane {
content.setText(text);
acceptButton.setOnMouseClicked(e -> {
closeConsumer.accept(MessageDialogPane.this);
fireEvent(new DialogCloseEvent());
Optional.ofNullable(onAccept).ifPresent(Runnable::run);
});
@@ -82,22 +80,18 @@ public final class MessageDialogPane extends StackPane {
}
}
public MessageDialogPane(String text, String title, Consumer<Region> closeConsumer, Runnable onAccept, Runnable onCancel) {
this(text, title, closeConsumer, MessageBox.QUESTION_MESSAGE, onAccept);
public MessageDialogPane(String text, String title, Runnable onAccept, Runnable onCancel) {
this(text, title, MessageBox.QUESTION_MESSAGE, onAccept);
cancelButton.setVisible(true);
cancelButton.setOnMouseClicked(e -> {
closeConsumer.accept(MessageDialogPane.this);
fireEvent(new DialogCloseEvent());
Optional.ofNullable(onCancel).ifPresent(Runnable::run);
});
acceptButton.setText(Launcher.i18n("button.yes"));
cancelButton.setText(Launcher.i18n("button.no"));
acceptButton.setText(i18n("button.yes"));
cancelButton.setText(i18n("button.no"));
actions.getChildren().add(cancelButton);
}
public void disableClosingDialog() {
closingDialog = false;
}
}
@@ -13,16 +13,17 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.ui.FXUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Collection;
import java.util.Optional;
import java.util.function.Consumer;
public class MultiColorItem extends ComponentList {
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Launcher.i18n("selector.custom"));
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Launcher.i18n("selector.choose_file"));
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", i18n("selector.custom"));
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file"));
private final ToggleGroup group = new ToggleGroup();
private final JFXColorPicker colorPicker = new JFXColorPicker();
@@ -37,19 +37,20 @@ import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.util.Collection;
import java.util.function.Consumer;
public class MultiFileItem extends ComponentList {
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", Launcher.i18n("selector.custom"));
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", Launcher.i18n("selector.choose_file"));
private final StringProperty customTitle = new SimpleStringProperty(this, "customTitle", i18n("selector.custom"));
private final StringProperty chooserTitle = new SimpleStringProperty(this, "chooserTitle", i18n("selector.choose_file"));
private final BooleanProperty directory = new SimpleBooleanProperty(this, "directory", false);
private final ObservableList<FileChooser.ExtensionFilter> extensionFilters = FXCollections.observableArrayList();
@@ -147,7 +148,7 @@ public class MultiFileItem extends ComponentList {
public void onExploreJavaDir() {
DirectoryChooser chooser = new DirectoryChooser();
chooser.setTitle(Launcher.i18n(getChooserTitle()));
chooser.setTitle(i18n(getChooserTitle()));
File selectedDir = chooser.showDialog(Controllers.getStage());
if (selectedDir != null)
txtCustom.setText(selectedDir.getAbsolutePath());
@@ -19,13 +19,14 @@ package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.beans.property.StringProperty;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.wizard.AbstractWizardDisplayer;
import org.jackhuang.hmcl.util.StringUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Map;
@@ -34,11 +35,11 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
@Override
default void handleTask(Map<String, Object> settings, Task task) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(it -> {
Controllers.closeDialog(it);
it.fireEvent(new DialogCloseEvent());
Controllers.navigate(null);
});
pane.setTitle(Launcher.i18n("message.doing"));
pane.setTitle(i18n("message.doing"));
pane.setProgress(Double.MAX_VALUE);
if (settings.containsKey("title")) {
Object title = settings.get("title");
@@ -61,12 +62,12 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
@Override
public void onStop(boolean success, TaskExecutor executor) {
JFXUtilities.runInFX(() -> {
Controllers.closeDialog(pane);
pane.fireEvent(new DialogCloseEvent());
if (success) {
if (settings.containsKey("success_message") && settings.get("success_message") instanceof String)
Controllers.dialog((String) settings.get("success_message"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
else if (!settings.containsKey("forbid_success_message"))
Controllers.dialog(Launcher.i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
Controllers.dialog(i18n("message.success"), null, MessageBox.FINE_MESSAGE, () -> Controllers.navigate(null));
} else {
if (executor.getLastException() == null)
return;
@@ -74,7 +75,7 @@ public interface TaskExecutorDialogWizardDisplayer extends AbstractWizardDisplay
if (settings.containsKey("failure_message") && settings.get("failure_message") instanceof String)
Controllers.dialog(appendix, (String) settings.get("failure_message"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
else if (!settings.containsKey("forbid_failure_message"))
Controllers.dialog(appendix, Launcher.i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
Controllers.dialog(appendix, i18n("wizard.failed"), MessageBox.ERROR_MESSAGE, () -> Controllers.navigate(null));
}
});
@@ -23,7 +23,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.forge.ForgeInstallTask;
import org.jackhuang.hmcl.download.game.GameAssetDownloadTask;
import org.jackhuang.hmcl.download.game.GameAssetRefreshTask;
@@ -35,6 +35,7 @@ import org.jackhuang.hmcl.mod.*;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.task.TaskListener;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.HashMap;
import java.util.Map;
@@ -62,29 +63,29 @@ public final class TaskListPane extends StackPane {
return;
if (task instanceof GameAssetRefreshTask) {
task.setName(Launcher.i18n("assets.download"));
task.setName(i18n("assets.download"));
} else if (task instanceof GameAssetDownloadTask) {
task.setName(Launcher.i18n("assets.download_all"));
task.setName(i18n("assets.download_all"));
} else if (task instanceof ForgeInstallTask) {
task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.forge")));
task.setName(i18n("install.installer.install", i18n("install.installer.forge")));
} else if (task instanceof LiteLoaderInstallTask) {
task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.liteloader")));
task.setName(i18n("install.installer.install", i18n("install.installer.liteloader")));
} else if (task instanceof OptiFineInstallTask) {
task.setName(Launcher.i18n("install.installer.install", Launcher.i18n("install.installer.optifine")));
task.setName(i18n("install.installer.install", i18n("install.installer.optifine")));
} else if (task instanceof CurseCompletionTask) {
task.setName(Launcher.i18n("modpack.type.curse.completion"));
task.setName(i18n("modpack.type.curse.completion"));
} else if (task instanceof ModpackInstallTask) {
task.setName(Launcher.i18n("modpack.installing"));
task.setName(i18n("modpack.installing"));
} else if (task instanceof CurseInstallTask) {
task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.curse")));
task.setName(i18n("modpack.install", i18n("modpack.type.curse")));
} else if (task instanceof MultiMCModpackInstallTask) {
task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.multimc")));
task.setName(i18n("modpack.install", i18n("modpack.type.multimc")));
} else if (task instanceof HMCLModpackInstallTask) {
task.setName(Launcher.i18n("modpack.install", Launcher.i18n("modpack.type.hmcl")));
task.setName(i18n("modpack.install", i18n("modpack.type.hmcl")));
} else if (task instanceof HMCLModpackExportTask) {
task.setName(Launcher.i18n("modpack.export"));
task.setName(i18n("modpack.export"));
} else if (task instanceof MinecraftInstanceTask) {
task.setName(Launcher.i18n("modpack.scan"));
task.setName(i18n("modpack.scan"));
}
ProgressListNode node = new ProgressListNode(task);
@@ -22,7 +22,7 @@ import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.GameRepository;
@@ -30,12 +30,11 @@ import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.Lang;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Map;
import java.util.Optional;
import static org.jackhuang.hmcl.Launcher.i18n;
class AdditionalInstallersPage extends StackPane implements WizardPage {
private final InstallerWizardProvider provider;
private final WizardController controller;
@@ -100,7 +99,7 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
@Override
public String getTitle() {
return Launcher.i18n("settings.tabs.installers");
return i18n("settings.tabs.installers");
}
private String getVersion(String id) {
@@ -109,24 +108,24 @@ class AdditionalInstallersPage extends StackPane implements WizardPage {
@Override
public void onNavigate(Map<String, Object> settings) {
lblGameVersion.setText(Launcher.i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + provider.getGameVersion());
btnForge.setDisable(provider.getForge() != null);
if (provider.getForge() != null || controller.getSettings().containsKey("forge"))
lblForge.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge")));
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + Lang.nonNull(provider.getForge(), getVersion("forge")));
else
lblForge.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.forge")));
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
btnLiteLoader.setDisable(provider.getLiteLoader() != null);
if (provider.getLiteLoader() != null || controller.getSettings().containsKey("liteloader"))
lblLiteLoader.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader")));
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + Lang.nonNull(provider.getLiteLoader(), getVersion("liteloader")));
else
lblLiteLoader.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.liteloader")));
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
btnOptiFine.setDisable(provider.getOptiFine() != null);
if (provider.getOptiFine() != null || controller.getSettings().containsKey("optifine"))
lblOptiFine.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine")));
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + Lang.nonNull(provider.getOptiFine(), getVersion("optifine")));
else
lblOptiFine.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.optifine")));
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
}
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.ui.download;
import javafx.scene.Node;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.GameBuilder;
import org.jackhuang.hmcl.download.RemoteVersion;
@@ -30,12 +30,11 @@ import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import org.jackhuang.hmcl.util.Lang;
import java.io.File;
import java.util.Map;
import static org.jackhuang.hmcl.Launcher.i18n;
import static org.jackhuang.hmcl.util.Lang.tryCast;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class DownloadWizardProvider implements WizardProvider {
private Profile profile;
@@ -78,8 +77,8 @@ public final class DownloadWizardProvider implements WizardProvider {
@Override
public Object finish(Map<String, Object> settings) {
settings.put("success_message", Launcher.i18n("install.success"));
settings.put("failure_message", Launcher.i18n("install.failed"));
settings.put("success_message", i18n("install.success"));
settings.put("failure_message", i18n("install.failed"));
switch (Lang.parseInt(settings.get(InstallTypePage.INSTALL_TYPE), -1)) {
case 0: return finishVersionDownloadingAsync(settings);
@@ -20,10 +20,11 @@ package org.jackhuang.hmcl.ui.download;
import com.jfoenix.controls.JFXListView;
import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Map;
@@ -49,7 +50,7 @@ public final class InstallTypePage extends StackPane implements WizardPage {
@Override
public String getTitle() {
return Launcher.i18n("install.select");
return i18n("install.select");
}
public static final String INSTALL_TYPE = "INSTALL_TYPE";
@@ -18,7 +18,7 @@
package org.jackhuang.hmcl.ui.download;
import javafx.scene.Node;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.Version;
@@ -26,6 +26,7 @@ import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardProvider;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Map;
@@ -80,8 +81,8 @@ public final class InstallerWizardProvider implements WizardProvider {
@Override
public Object finish(Map<String, Object> settings) {
settings.put("success_message", Launcher.i18n("install.success"));
settings.put("failure_message", Launcher.i18n("install.failed"));
settings.put("success_message", i18n("install.success"));
settings.put("failure_message", i18n("install.failed"));
Task ret = Task.empty();
@@ -23,7 +23,7 @@ import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.game.GameRepository;
@@ -32,6 +32,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.StringUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Map;
@@ -75,30 +76,30 @@ public class InstallersPage extends StackPane implements WizardPage {
String gameVersion = ((RemoteVersion<?>) controller.getSettings().get("game")).getGameVersion();
Validator hasVersion = new Validator(s -> !repository.hasVersion(s) && StringUtils.isNotBlank(s));
hasVersion.setMessage(Launcher.i18n("install.new_game.already_exists"));
hasVersion.setMessage(i18n("install.new_game.already_exists"));
txtName.getValidators().add(hasVersion);
txtName.textProperty().addListener(e -> btnInstall.setDisable(!txtName.validate()));
txtName.setText(gameVersion);
btnForge.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 0);
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.forge")), gameVersion, downloadProvider, "forge", () -> controller.onPrev(false)));
});
btnLiteLoader.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 1);
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.liteloader")), gameVersion, downloadProvider, "liteloader", () -> controller.onPrev(false)));
});
btnOptiFine.setOnMouseClicked(e -> {
controller.getSettings().put(INSTALLER_TYPE, 2);
controller.onNext(new VersionsPage(controller, Launcher.i18n("install.installer.choose", Launcher.i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
controller.onNext(new VersionsPage(controller, i18n("install.installer.choose", i18n("install.installer.optifine")), gameVersion, downloadProvider, "optifine", () -> controller.onPrev(false)));
});
}
@Override
public String getTitle() {
return Launcher.i18n("install.new_game");
return i18n("install.new_game");
}
private String getVersion(String id) {
@@ -107,21 +108,21 @@ public class InstallersPage extends StackPane implements WizardPage {
@Override
public void onNavigate(Map<String, Object> settings) {
lblGameVersion.setText(Launcher.i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
lblGameVersion.setText(i18n("install.new_game.current_game_version") + ": " + getVersion("game"));
if (controller.getSettings().containsKey("forge"))
lblForge.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.forge")) + ": " + getVersion("forge"));
lblForge.setText(i18n("install.installer.version", i18n("install.installer.forge")) + ": " + getVersion("forge"));
else
lblForge.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.forge")));
lblForge.setText(i18n("install.installer.not_installed", i18n("install.installer.forge")));
if (controller.getSettings().containsKey("liteloader"))
lblLiteLoader.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
lblLiteLoader.setText(i18n("install.installer.version", i18n("install.installer.liteloader")) + ": " + getVersion("liteloader"));
else
lblLiteLoader.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.liteloader")));
lblLiteLoader.setText(i18n("install.installer.not_installed", i18n("install.installer.liteloader")));
if (controller.getSettings().containsKey("optifine"))
lblOptiFine.setText(Launcher.i18n("install.installer.version", Launcher.i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
lblOptiFine.setText(i18n("install.installer.version", i18n("install.installer.optifine")) + ": " + getVersion("optifine"));
else
lblOptiFine.setText(Launcher.i18n("install.installer.not_installed", Launcher.i18n("install.installer.optifine")));
lblOptiFine.setText(i18n("install.installer.not_installed", i18n("install.installer.optifine")));
}
@Override
@@ -25,7 +25,7 @@ import javafx.scene.control.Label;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
@@ -37,6 +37,7 @@ import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.StringUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.util.Map;
@@ -75,16 +76,16 @@ public final class ModpackPage extends StackPane implements WizardPage {
Profile profile = (Profile) controller.getSettings().get("PROFILE");
FileChooser chooser = new FileChooser();
chooser.setTitle(Launcher.i18n("modpack.choose"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
chooser.setTitle(i18n("modpack.choose"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile == null) Platform.runLater(() -> Controllers.navigate(null));
else {
controller.getSettings().put(MODPACK_FILE, selectedFile);
lblModpackLocation.setText(selectedFile.getAbsolutePath());
txtModpackName.getValidators().addAll(
new Validator(Launcher.i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)),
new Validator(Launcher.i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str))
new Validator(i18n("install.new_game.already_exists"), str -> !profile.getRepository().hasVersion(str) && StringUtils.isNotBlank(str)),
new Validator(i18n("version.forbidden_name"), str -> !profile.getRepository().forbidsVersion(str))
);
txtModpackName.textProperty().addListener(e -> btnInstall.setDisable(!txtModpackName.validate()));
@@ -96,7 +97,7 @@ public final class ModpackPage extends StackPane implements WizardPage {
lblAuthor.setText(manifest.getAuthor());
txtModpackName.setText(manifest.getName() + (StringUtils.isBlank(manifest.getVersion()) ? "" : "-" + manifest.getVersion()));
} catch (UnsupportedModpackException e) {
txtModpackName.setText(Launcher.i18n("modpack.task.install.error"));
txtModpackName.setText(i18n("modpack.task.install.error"));
}
}
}
@@ -118,14 +119,14 @@ public final class ModpackPage extends StackPane implements WizardPage {
if (manifest != null) {
WebStage stage = new WebStage();
stage.getWebView().getEngine().loadContent(manifest.getDescription());
stage.setTitle(Launcher.i18n("modpack.wizard.step.3"));
stage.setTitle(i18n("modpack.wizard.step.3"));
stage.showAndWait();
}
}
@Override
public String getTitle() {
return Launcher.i18n("modpack.task.install");
return i18n("modpack.task.install");
}
public static final String MODPACK_FILE = "MODPACK_FILE";
@@ -23,12 +23,13 @@ import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.download.RemoteVersion;
import org.jackhuang.hmcl.download.game.GameRemoteVersionTag;
import org.jackhuang.hmcl.download.liteloader.LiteLoaderRemoteVersionTag;
import org.jackhuang.hmcl.download.optifine.OptiFineRemoteVersion;
import org.jackhuang.hmcl.ui.FXUtils;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.Objects;
@@ -57,15 +58,15 @@ public final class VersionsPageItem extends StackPane {
if (remoteVersion.getTag() instanceof GameRemoteVersionTag) {
switch (((GameRemoteVersionTag) remoteVersion.getTag()).getType()) {
case RELEASE:
lblGameVersion.setText(Launcher.i18n("version.game.release"));
lblGameVersion.setText(i18n("version.game.release"));
imageView.setImage(new Image("/assets/img/icon.png", 32, 32, false, true));
break;
case SNAPSHOT:
lblGameVersion.setText(Launcher.i18n("version.game.snapshot"));
lblGameVersion.setText(i18n("version.game.snapshot"));
imageView.setImage(new Image("/assets/img/command.png", 32, 32, false, true));
break;
default:
lblGameVersion.setText(Launcher.i18n("version.game.old"));
lblGameVersion.setText(i18n("version.game.old"));
imageView.setImage(new Image("/assets/img/grass.png", 32, 32, false, true));
break;
}
@@ -23,6 +23,7 @@ import org.jackhuang.hmcl.game.HMCLModpackExportTask;
import org.jackhuang.hmcl.game.HMCLModpackManager;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.setting.Config;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Task;
@@ -93,7 +94,7 @@ public final class ExportWizardProvider implements WizardProvider {
config.logLines.set(100);
config.configurations.clear();
zip.putTextFile(Settings.GSON.toJson(config), Settings.SETTINGS_FILE_NAME);
zip.putTextFile(config.toJson(), ConfigHolder.CONFIG_FILENAME);
zip.putFile(tempModpack, "modpack.zip");
File bg = new File("bg").getAbsoluteFile();
@@ -25,7 +25,7 @@ import javafx.scene.control.Label;
import javafx.scene.control.TreeItem;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.game.ModAdviser;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.FXUtils;
@@ -34,7 +34,6 @@ import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
@@ -43,6 +42,7 @@ import java.util.Objects;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
* @author huangyuhui
@@ -151,23 +151,23 @@ public final class ModpackFileSelectionPage extends StackPane implements WizardP
@Override
public String getTitle() {
return Launcher.i18n("modpack.wizard.step.2.title");
return i18n("modpack.wizard.step.2.title");
}
public static final String MODPACK_FILE_SELECTION = "modpack.accepted";
private static final Map<String, String> TRANSLATION = mapOf(
pair("minecraft/servers.dat", Launcher.i18n("modpack.files.servers_dat")),
pair("minecraft/saves", Launcher.i18n("modpack.files.saves")),
pair("minecraft/mods", Launcher.i18n("modpack.files.mods")),
pair("minecraft/config", Launcher.i18n("modpack.files.config")),
pair("minecraft/liteconfig", Launcher.i18n("modpack.files.liteconfig")),
pair("minecraft/resourcepacks", Launcher.i18n("modpack.files.resourcepacks")),
pair("minecraft/resources", Launcher.i18n("modpack.files.resourcepacks")),
pair("minecraft/options.txt", Launcher.i18n("modpack.files.options_txt")),
pair("minecraft/optionsshaders.txt", Launcher.i18n("modpack.files.optionsshaders_txt")),
pair("minecraft/mods/VoxelMods", Launcher.i18n("modpack.files.mods.voxelmods")),
pair("minecraft/dumps", Launcher.i18n("modpack.files.dumps")),
pair("minecraft/blueprints", Launcher.i18n("modpack.files.blueprints")),
pair("minecraft/scripts", Launcher.i18n("modpack.files.scripts"))
pair("minecraft/servers.dat", i18n("modpack.files.servers_dat")),
pair("minecraft/saves", i18n("modpack.files.saves")),
pair("minecraft/mods", i18n("modpack.files.mods")),
pair("minecraft/config", i18n("modpack.files.config")),
pair("minecraft/liteconfig", i18n("modpack.files.liteconfig")),
pair("minecraft/resourcepacks", i18n("modpack.files.resourcepacks")),
pair("minecraft/resources", i18n("modpack.files.resourcepacks")),
pair("minecraft/options.txt", i18n("modpack.files.options_txt")),
pair("minecraft/optionsshaders.txt", i18n("modpack.files.optionsshaders_txt")),
pair("minecraft/mods/VoxelMods", i18n("modpack.files.mods.voxelmods")),
pair("minecraft/dumps", i18n("modpack.files.dumps")),
pair("minecraft/blueprints", i18n("modpack.files.blueprints")),
pair("minecraft/scripts", i18n("modpack.files.scripts"))
);
}
@@ -33,6 +33,7 @@ import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.wizard.WizardController;
import org.jackhuang.hmcl.ui.wizard.WizardPage;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.util.List;
@@ -81,8 +82,8 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
@FXML
private void onNext() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle(Launcher.i18n("modpack.wizard.step.initialization.save"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Launcher.i18n("modpack"), "*.zip"));
fileChooser.setTitle(i18n("modpack.wizard.step.initialization.save"));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(i18n("modpack"), "*.zip"));
File file = fileChooser.showSaveDialog(Controllers.getStage());
if (file == null) {
Controllers.navigate(null);
@@ -109,7 +110,7 @@ public final class ModpackInfoPage extends StackPane implements WizardPage {
@Override
public String getTitle() {
return Launcher.i18n("modpack.wizard.step.1.title");
return i18n("modpack.wizard.step.1.title");
}
public static final String MODPACK_NAME = "modpack.name";
@@ -27,8 +27,10 @@ import org.jackhuang.hmcl.task.FileDownloadTask.IntegrityCheck;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.*;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.File;
import java.io.FileInputStream;
@@ -113,13 +115,13 @@ public class AppDataUpgrader extends IUpgrader {
Task task = new AppDataUpgraderJarTask(NetworkUtils.toURL(map.get("jar")), version.toString(), hash);
TaskExecutor executor = task.executor();
AtomicReference<Region> region = new AtomicReference<>();
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, i18n("message.downloading"), "", null)));
if (executor.test()) {
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderJarTask.getSelf(version.toString()).getAbsolutePath())
.directory(new File("").getAbsoluteFile()).start();
System.exit(0);
}
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
}
@@ -131,13 +133,13 @@ public class AppDataUpgrader extends IUpgrader {
Task task = new AppDataUpgraderPackGzTask(NetworkUtils.toURL(map.get("pack")), version.toString(), hash);
TaskExecutor executor = task.executor();
AtomicReference<Region> region = new AtomicReference<>();
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, i18n("message.downloading"), "", null)));
if (executor.test()) {
new ProcessBuilder(JavaVersion.fromCurrentEnvironment().getBinary().getAbsolutePath(), "-jar", AppDataUpgraderPackGzTask.getSelf(version.toString()).getAbsolutePath())
.directory(new File("").getAbsoluteFile()).start();
System.exit(0);
}
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
} catch (IOException ex) {
Logging.LOG.log(Level.SEVERE, "Failed to create upgrader", ex);
}
@@ -153,7 +155,7 @@ public class AppDataUpgrader extends IUpgrader {
} catch (URISyntaxException | IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to browse uri: " + url, e);
OperatingSystem.setClipboard(url);
MessageBox.show(Launcher.i18n("update.no_browser"));
MessageBox.show(i18n("update.no_browser"));
}
}
})).start();
@@ -19,14 +19,14 @@ package org.jackhuang.hmcl.upgrade;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.VersionNumber;
import java.io.File;
import java.io.IOException;
import java.net.URL;
@@ -35,6 +35,7 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
*
@@ -57,11 +58,11 @@ public class NewFileUpgrader extends IUpgrader {
URL url = requestDownloadLink();
if (url == null) return;
File newf = new File(url.getFile());
Controllers.dialog(Launcher.i18n("message.downloading"));
Controllers.dialog(i18n("message.downloading"));
Task task = new FileDownloadTask(url, newf);
TaskExecutor executor = task.executor();
AtomicReference<Region> region = new AtomicReference<>();
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, Launcher.i18n("message.downloading"), "", null)));
JFXUtilities.runInFX(() -> region.set(Controllers.taskDialog(executor, i18n("message.downloading"), "", null)));
if (executor.test()) {
try {
new ProcessBuilder(newf.getCanonicalPath(), "--removeOldLauncher", getRealPath())
@@ -72,7 +73,7 @@ public class NewFileUpgrader extends IUpgrader {
}
System.exit(0);
}
JFXUtilities.runInFX(() -> Controllers.closeDialog(region.get()));
JFXUtilities.runInFX(() -> region.get().fireEvent(new DialogCloseEvent()));
}
private static String getRealPath() {
@@ -29,8 +29,8 @@ import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.NetworkUtils;
import org.jackhuang.hmcl.util.VersionNumber;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.IOException;
import java.util.Collection;
@@ -92,7 +92,7 @@ public final class UpdateChecker {
if (value == null) {
LOG.warning("Unable to check update...");
if (showMessage)
MessageBox.show(Launcher.i18n("update.failed"));
MessageBox.show(i18n("update.failed"));
} else if (base.compareTo(value) < 0)
outOfDate = true;
if (outOfDate)
@@ -21,9 +21,9 @@ import javafx.application.Platform;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.ui.CrashWindow;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import static java.util.Collections.newSetFromMap;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.io.IOException;
import java.text.SimpleDateFormat;
@@ -41,19 +41,17 @@ public class CrashReporter implements Thread.UncaughtExceptionHandler {
private static final Map<String, String> SOURCE = new HashMap<String, String>() {
{
put("javafx.fxml.LoadException", Launcher.i18n("crash.NoClassDefFound"));
put("Location is not set", Launcher.i18n("crash.NoClassDefFound"));
put("UnsatisfiedLinkError", Launcher.i18n("crash.user_fault"));
put("java.lang.NoClassDefFoundError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.VerifyError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.NoSuchMethodError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.NoSuchFieldError", Launcher.i18n("crash.NoClassDefFound"));
put("netscape.javascript.JSException", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.IncompatibleClassChangeError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.ClassFormatError", Launcher.i18n("crash.NoClassDefFound"));
put("java.lang.OutOfMemoryError", "FUCKING MEMORY LIMIT!");
put("Trampoline", Launcher.i18n("launcher.update_java"));
put("com.sun.javafx.css.StyleManager.findMatchingStyles", Launcher.i18n("launcher.update_java"));
put("javafx.fxml.LoadException", i18n("crash.NoClassDefFound"));
put("Location is not set", i18n("crash.NoClassDefFound"));
put("UnsatisfiedLinkError", i18n("crash.user_fault"));
put("java.lang.NoClassDefFoundError", i18n("crash.NoClassDefFound"));
put("java.lang.VerifyError", i18n("crash.NoClassDefFound"));
put("java.lang.NoSuchMethodError", i18n("crash.NoClassDefFound"));
put("java.lang.NoSuchFieldError", i18n("crash.NoClassDefFound"));
put("netscape.javascript.JSException", i18n("crash.NoClassDefFound"));
put("java.lang.IncompatibleClassChangeError", i18n("crash.NoClassDefFound"));
put("java.lang.ClassFormatError", i18n("crash.NoClassDefFound"));
put("com.sun.javafx.css.StyleManager.findMatchingStyles", i18n("launcher.update_java"));
put("NoSuchAlgorithmException", "Has your operating system been installed completely or is a ghost system?");
}
};
@@ -0,0 +1,59 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.util.i18n;
import static org.jackhuang.hmcl.util.Logging.LOG;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.logging.Level;
import org.jackhuang.hmcl.setting.Settings;
public final class I18n {
private I18n() {}
private static ResourceBundle RESOURCE_BUNDLE = null;
static {
try {
RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
} catch (Throwable e) {
LOG.log(Level.SEVERE, "Settings cannot be initialized", e);
}
}
public static ResourceBundle getResourceBundle() {
return RESOURCE_BUNDLE == null ? Locales.DEFAULT.getResourceBundle() : RESOURCE_BUNDLE;
}
public static String i18n(String key, Object... formatArgs) {
return String.format(i18n(key), formatArgs);
}
public static String i18n(String key) {
try {
return getResourceBundle().getString(key);
} catch (MissingResourceException e) {
LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
return key;
}
}
}
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -15,9 +15,8 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
package org.jackhuang.hmcl.util.i18n;
import org.jackhuang.hmcl.ui.construct.UTF8Control;
import org.jackhuang.hmcl.util.Lang;
import java.util.List;
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -15,7 +15,9 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
package org.jackhuang.hmcl.util.i18n;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.io.InputStream;
@@ -26,14 +28,14 @@ import java.util.Locale;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
public final class UTF8Control extends ResourceBundle.Control {
final class UTF8Control extends ResourceBundle.Control {
public static final UTF8Control INSTANCE = new UTF8Control();
private UTF8Control() {}
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IOException {
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IOException {
// The below is a copy of the default implementation.
String bundleName = toBundleName(baseName, locale);
String resourceName = toResourceName(bundleName, "properties");
@@ -54,7 +56,7 @@ public final class UTF8Control extends ResourceBundle.Control {
if (stream != null) {
try {
// Only this line is changed to make it to read properties files as UTF-8.
bundle = new PropertyResourceBundle(new InputStreamReader(stream, "UTF-8"));
bundle = new PropertyResourceBundle(new InputStreamReader(stream, UTF_8));
} finally {
stream.close();
}
@@ -569,7 +569,6 @@
.list-cell:selected, .jfx-list-cell:selected {
-fx-background-insets: 0.0;
-fx-text-fill: BLACK;
-fx-background-color: black;
}
.jfx-list-cell:filled:hover,
@@ -29,7 +29,7 @@
<Label fx:id="lblInjectorServer" text="%account.injector.server" GridPane.halignment="RIGHT"
GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<JFXComboBox fx:id="cboServers" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<JFXComboBox fx:id="cboServers" promptText="%general.prompt.empty" maxHeight="25" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Hyperlink fx:id="linkManageInjectorServers" text="%account.injector.manage"
onMouseClicked="#onManageInjecterServers" GridPane.columnIndex="2" GridPane.rowIndex="1"/>
@@ -37,7 +37,7 @@
<Label text="%account.username" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
<JFXTextField fx:id="txtUsername" GridPane.columnIndex="1" GridPane.rowIndex="2" GridPane.columnSpan="2"
FXUtils.validateWhileTextChanged="true">
FXUtils.validateWhileTextChanged="true" onAction="#onCreationAccept">
<validators>
<RequiredFieldValidator message="%input.not_empty">
</RequiredFieldValidator>
@@ -47,7 +47,7 @@
<Label fx:id="lblPassword" text="%account.password" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
<JFXPasswordField fx:id="txtPassword" GridPane.columnIndex="1" GridPane.rowIndex="3"
GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true">
GridPane.columnSpan="2" FXUtils.validateWhileTextChanged="true" onAction="#onCreationAccept">
<validators>
<RequiredFieldValidator message="%input.not_empty">
</RequiredFieldValidator>
@@ -2,6 +2,7 @@
<?import com.jfoenix.controls.JFXButton?>
<?import javafx.scene.control.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
<?import com.jfoenix.controls.JFXProgressBar?>
@@ -20,6 +21,9 @@
</left>
<right>
<VBox>
<BorderPane.margin>
<Insets left="15.0" />
</BorderPane.margin>
<Label fx:id="lblType" BorderPane.alignment="CENTER_LEFT"/>
</VBox>
</right>
@@ -33,6 +37,9 @@
</left>
<right>
<VBox>
<BorderPane.margin>
<Insets left="15.0" />
</BorderPane.margin>
<Label fx:id="lblCharacter" BorderPane.alignment="CENTER_LEFT"/>
</VBox>
</right>
@@ -46,6 +53,9 @@
</left>
<right>
<VBox>
<BorderPane.margin>
<Insets left="15.0" />
</BorderPane.margin>
<Label fx:id="lblEmail" BorderPane.alignment="CENTER_LEFT"/>
</VBox>
</right>
@@ -59,24 +69,26 @@
</left>
<right>
<VBox>
<BorderPane.margin>
<Insets left="15.0" />
</BorderPane.margin>
<Label fx:id="lblServer" BorderPane.alignment="CENTER_LEFT"/>
</VBox>
</right>
</BorderPane>
</ComponentList>
<BorderPane pickOnBounds="false" style="-fx-padding: 4;">
<left>
<JFXButton BorderPane.alignment="BOTTOM_LEFT" fx:id="btnDelete" onMouseClicked="#onDelete"
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
prefWidth="30" prefHeight="30"/>
</left>
<right>
<JFXButton BorderPane.alignment="BOTTOM_RIGHT" fx:id="btnRefresh" onMouseClicked="#onRefresh"
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
prefWidth="30" prefHeight="30"/>
</right>
</BorderPane>
</VBox>
<BorderPane pickOnBounds="false" style="-fx-padding: 4;">
<left>
<JFXButton BorderPane.alignment="BOTTOM_LEFT" fx:id="btnDelete" onMouseClicked="#onDelete"
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
prefWidth="30" prefHeight="30"/>
</left>
<right>
<JFXButton BorderPane.alignment="BOTTOM_RIGHT" fx:id="btnRefresh" onMouseClicked="#onRefresh"
styleClass="toggle-icon4" maxWidth="30" maxHeight="30" minWidth="30" minHeight="30"
prefWidth="30" prefHeight="30"/>
</right>
</BorderPane>
<JFXProgressBar fx:id="progressBar" StackPane.alignment="TOP_CENTER" visible="false" />
</fx:root>
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.String?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.jfoenix.controls.*?>
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
<StackPane fx:id="addServerContainer">
<JFXDialogLayout fx:id="addServerPane">
<heading>
<Label text="%account.injector.add" />
</heading>
<body>
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url" onAction="#onAddNext">
<validators>
<URLValidator message="%input.url">
<protocols>
<String fx:value="http" />
<String fx:value="https" />
</protocols>
</URLValidator>
</validators>
</JFXTextField>
</body>
<actions>
<Label fx:id="lblCreationWarning" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
</SpinnerPane>
</actions>
</JFXDialogLayout>
<JFXDialogLayout fx:id="confirmServerPane">
<heading>
<Label text="%account.injector.add" />
</heading>
<body>
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
<columnConstraints>
<ColumnConstraints maxWidth="100" />
<ColumnConstraints />
</columnConstraints>
<Label text="%account.injector.server_url" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<Label fx:id="lblServerUrl" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label fx:id="lblServerWarning" text="%account.injector.http" style="-fx-text-fill: red;"
GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
</GridPane>
</body>
<actions>
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
</actions>
</JFXDialogLayout>
</StackPane>
</fx:root>
@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import org.jackhuang.hmcl.ui.construct.URLValidator?>
<?import java.lang.String?>
<?import org.jackhuang.hmcl.ui.construct.SpinnerPane?>
<?import com.jfoenix.controls.*?>
<fx:root xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
type="StackPane">
@@ -23,61 +21,4 @@
</JFXButton>
</VBox>
</StackPane>
<JFXDialog fx:id="dialog">
<StackPane>
<StackPane fx:id="addServerContainer">
<JFXDialogLayout fx:id="addServerPane">
<heading>
<Label text="%account.injector.add" />
</heading>
<body>
<JFXTextField fx:id="txtServerUrl" promptText="%account.injector.server_url">
<validators>
<URLValidator message="%input.url">
<protocols>
<String fx:value="http" />
<String fx:value="https" />
</protocols>
</URLValidator>
</validators>
</JFXTextField>
</body>
<actions>
<Label fx:id="lblCreationWarning" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<SpinnerPane fx:id="nextPane" styleClass="small-spinner-pane">
<JFXButton fx:id="btnAddNext" onMouseClicked="#onAddNext" text="%wizard.next" styleClass="dialog-accept" />
</SpinnerPane>
</actions>
</JFXDialogLayout>
<JFXDialogLayout fx:id="confirmServerPane">
<heading>
<Label text="%account.injector.add" />
</heading>
<body>
<GridPane vgap="15" hgap="15" style="-fx-padding: 15 0 0 0;">
<columnConstraints>
<ColumnConstraints maxWidth="100" />
<ColumnConstraints />
</columnConstraints>
<Label text="%account.injector.server_url" GridPane.columnIndex="0" GridPane.rowIndex="0" />
<Label text="%account.injector.server_name" GridPane.columnIndex="0" GridPane.rowIndex="1" />
<Label fx:id="lblServerUrl" GridPane.columnIndex="1" GridPane.rowIndex="0" />
<Label fx:id="lblServerName" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label fx:id="lblServerWarning" text="%account.injector.http" style="-fx-text-fill: red;" GridPane.rowIndex="2" GridPane.columnSpan="2" GridPane.columnIndex="0" />
</GridPane>
</body>
<actions>
<JFXButton onMouseClicked="#onAddPrev" text="%wizard.prev" styleClass="dialog-cancel" />
<JFXButton onMouseClicked="#onAddCancel" text="%button.cancel" styleClass="dialog-cancel" />
<JFXButton onMouseClicked="#onAddFinish" text="%wizard.finish" styleClass="dialog-accept" />
</actions>
</JFXDialogLayout>
</StackPane>
</StackPane>
</JFXDialog>
</fx:root>
@@ -41,8 +41,9 @@ account.failed.invalid_token=Please log out and re-input your password to login.
account.failed.no_character=No character in this account.
account.failed.no_selected_server=No authentication server is selected.
account.failed.connect_injector_server=Cannot connect to the authentication server. Check your network and ensure the URL is correct.
account.injector.add=Add authentication server
account.injector.add=Add an authentication server
account.injector.manage=Manage authentication servers
account.injector.manage.title=Authentication servers
account.injector.http=Warning: This server is using HTTP, which will cause your password be transmitted in clear text.
account.injector.server=Auth Server
account.injector.server_url=Server URL
@@ -52,7 +53,7 @@ account.methods.authlib_injector=authlib-injector
account.methods.no_method=No login method
account.methods.offline=Offline
account.methods.yggdrasil=Mojang
account.missing=Missing account
account.missing=None
account.password=Password
account.username=Name
@@ -91,6 +92,9 @@ extension.mod=Mod file
extension.png=Image file
extension.sh=Bash shell
fatal.missing_javafx=JavaFX is missing.\nIf you are using Java 11 or later, please downgrade to Java 8 or 10.\nIf you are using OpenJDK, please ensure OpenJFX is included.
fatal.missing_dst_root_ca_x3=The DST Root CA X3 certificate is missing on the current Java platform.\nYou can still use HMCL, but HMCL will be unable to connect to some sites (such as sites that use certificates issued by Let's Encrypt), which may cause HMCL not to function properly.\nPlease upgrade your Java to 8u101 or later to resolve the problem.
folder.config=Configs
folder.coremod=Core Mod
folder.game=Game Dir
@@ -124,7 +128,7 @@ install.select=Select an operation
install.success=Installed successfully
lang=English
lang.default=Belong to OS language.
lang.default=Use system language
launch.advice.different_platform=Your OS is 64-Bit but your Java is 32-Bit. The 64-Bit Java is recommended.
launch.advice.java8_1_13=Minecraft 1.13 and later versions can only run on Java 8 or later.
@@ -303,8 +307,8 @@ settings.type.special=Specialized version settings(will not affect other version
update=Update
update.failed=Failed to check for updates.
update.found=Found Update!
update.newest_version=Newest version: %s
update.found=Update Available!
update.newest_version=Latest version: %s
update.latest=This is latest Version.
update.no_browser=Cannot open any browser. The link has been copied to the clipboard. Paste it to a browser address bar to update.
update.tooltip=Update
@@ -43,6 +43,7 @@ account.failed.no_selected_server=未選擇認證服務器
account.failed.connect_injector_server=無法連接認證服務器,可能是網絡故障或 URL 輸入錯誤
account.injector.add=添加認證服務器
account.injector.manage=管理認證服務器
account.injector.manage.title=認證服務器
account.injector.http=警告:此服務器使用不安全的 HTTP 協議,您的密碼在登錄時會被明文傳輸。
account.injector.server=認證服務器
account.injector.server_url=服務器地址
@@ -91,6 +92,9 @@ extension.mod=模組文件
extension.png=圖片文件
extension.sh=Bash 腳本
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,請降級到 Java 8 或 10。\n如果您使用的是 OpenJDK,請確保其包含 OpenJFX。
fatal.missing_dst_root_ca_x3=當前 Java 平台缺少 DST Root CA X3 證書。\n您依然可以使用 HMCL,但將無法連接到部分站點(如使用 Let's Encrypt 證書的站點),這可能會使 HMCL 無法正常工作。\n請將您的 Java 升級到 8u101 以上以解決此問題。
folder.config=配置文件夾
folder.coremod=核心MOD文件夾
folder.game=遊戲文件夾
@@ -43,6 +43,7 @@ account.failed.no_selected_server=未选择认证服务器
account.failed.connect_injector_server=无法连接认证服务器,可能是网络故障或 URL 输入错误
account.injector.add=添加认证服务器
account.injector.manage=管理认证服务器
account.injector.manage.title=认证服务器
account.injector.http=警告:此服务器使用不安全的 HTTP 协议,您的密码在登录时会被明文传输。
account.injector.server=认证服务器
account.injector.server_url=服务器地址
@@ -91,6 +92,9 @@ extension.mod=模组文件
extension.png=图片文件
extension.sh=Bash 脚本
fatal.missing_javafx=JavaFX 缺失。\n如果您使用的是 Java 11 或更高版本,请降级到 Java 8 或 10。\n如果您使用的是 OpenJDK,请确保其包含 OpenJFX。
fatal.missing_dst_root_ca_x3=当前 Java 平台缺少 DST Root CA X3 证书。\n您依然可以使用 HMCL,但将无法连接到部分站点(如使用 Let's Encrypt 证书的站点),这可能会使 HMCL 无法正常工作。\n请将您的 Java 升级到 8u101 以上以解决此问题。
folder.config=配置文件夹
folder.coremod=核心MOD文件夹
folder.game=游戏文件夹
@@ -0,0 +1,71 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.auth.authlibinjector;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Optional;
import java.util.function.Consumer;
import javafx.event.EventHandler;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.TransferMode;
/**
* @author yushijinhun
* @see https://github.com/yushijinhun/authlib-injector/wiki/%E5%90%AF%E5%8A%A8%E5%99%A8%E6%8A%80%E6%9C%AF%E8%A7%84%E8%8C%83#dnd-%E6%96%B9%E5%BC%8F%E6%B7%BB%E5%8A%A0-yggdrasil-%E6%9C%8D%E5%8A%A1%E7%AB%AF
*/
public final class AuthlibInjectorDnD {
private static final String SCHEME = "authlib-injector";
private static final String PATH_YGGDRASIL_SERVER = "yggdrasil-server";
private AuthlibInjectorDnD() {}
public static Optional<String> parseUrlFromDragboard(Dragboard dragboard) {
String uri = dragboard.getString();
if (uri == null) return Optional.empty();
String[] uriElements = uri.split(":");
if (uriElements.length == 3 && SCHEME.equals(uriElements[0]) && PATH_YGGDRASIL_SERVER.equals(uriElements[1])) {
try {
return Optional.of(URLDecoder.decode(uriElements[2], "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException(e);
}
}
return Optional.empty();
}
public static EventHandler<DragEvent> dragOverHandler() {
return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {
event.acceptTransferModes(TransferMode.COPY);
event.consume();
});
}
public static EventHandler<DragEvent> dragDroppedHandler(Consumer<String> onUrlTransfered) {
return event -> parseUrlFromDragboard(event.getDragboard()).ifPresent(url -> {
event.setDropCompleted(true);
event.consume();
onUrlTransfered.accept(url);
});
}
}
@@ -26,8 +26,8 @@ import org.jackhuang.hmcl.util.JsonUtils;
import org.jackhuang.hmcl.util.NetworkUtils;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
public class AuthlibInjectorServer {
@@ -36,7 +36,7 @@ public class AuthlibInjectorServer {
JsonObject response = JsonUtils.fromNonNullJson(NetworkUtils.doGet(NetworkUtils.toURL(url)), JsonObject.class);
String name = extractServerName(response).orElse(url);
return new AuthlibInjectorServer(url, name);
} catch (JsonSyntaxException e) {
} catch (JsonParseException e) {
throw new IOException("Malformed response", e);
}
}
@@ -67,7 +67,8 @@ public class BMCLAPIDownloadProvider implements DownloadProvider {
.replace("https://libraries.minecraft.net", "https://bmclapi2.bangbang93.com/libraries")
.replaceFirst("https?://files\\.minecraftforge\\.net/maven", "https://bmclapi2.bangbang93.com/maven")
.replace("http://dl.liteloader.com/versions/versions.json", "https://bmclapi2.bangbang93.com/maven/com/mumfrey/liteloader/versions.json")
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven");
.replace("http://dl.liteloader.com/versions", "https://bmclapi2.bangbang93.com/maven")
.replace("https://authlib-injector.yushi.moe", "https://bmclapi2.bangbang93.com/mirrors/authlib-injector");
}
}
@@ -49,7 +49,8 @@ public final class LibraryDownloadTask extends Task {
setSignificance(TaskSignificance.MODERATE);
task = new FileDownloadTask(NetworkUtils.toURL(url),
file, dependencyManager.getProxy(), new IntegrityCheck("SHA-1", library.getDownload().getSha1()));
file, dependencyManager.getProxy(),
library.getDownload().getSha1() != null ? new IntegrityCheck("SHA-1", library.getDownload().getSha1()) : null);
}
@Override
@@ -56,8 +56,8 @@ public final class OptiFineBMCLVersionList extends VersionList<Void> {
List<OptiFineVersion> root = Constants.GSON.fromJson(task.getResult(), new TypeToken<List<OptiFineVersion>>() {
}.getType());
for (OptiFineVersion element : root) {
String version = element.getType();
if (version == null)
String version = element.getType() + "_" + element.getPatch();
if (element.getType() == null || "pre".equals(element.getPatch()))
continue;
String mirror = "http://bmclapi2.bangbang93.com/optifine/" + element.getGameVersion() + "/" + element.getType() + "/" + element.getPatch();
if (!duplicates.add(mirror))
@@ -58,12 +58,17 @@ public final class MultiMCInstanceConfiguration {
private final boolean overrideCommands; // OverrideCommands
private final boolean overrideWindow; // OverrideWindow
public MultiMCInstanceConfiguration(String defaultName, InputStream contentStream) throws IOException {
private final MultiMCManifest mmcPack;
private MultiMCInstanceConfiguration(String defaultName, InputStream contentStream, MultiMCManifest mmcPack) throws IOException {
Properties p = new Properties();
p.load(contentStream);
this.mmcPack = mmcPack;
autoCloseConsole = Boolean.parseBoolean(p.getProperty("AutoCloseConsole"));
gameVersion = p.getProperty("IntendedVersion");
gameVersion = mmcPack != null ? mmcPack.getComponents().stream().filter(e -> "net.minecraft".equals(e.getUid())).findAny()
.orElseThrow(() -> new IOException("Malformed mmc-pack.json")).getVersion() : p.getProperty("IntendedVersion");
javaPath = p.getProperty("JavaPath");
jvmArgs = p.getProperty("JvmArgs");
fullscreen = Boolean.parseBoolean(p.getProperty("LaunchMaximized"));
@@ -253,6 +258,10 @@ public final class MultiMCInstanceConfiguration {
return overrideWindow;
}
public MultiMCManifest getMmcPack() {
return mmcPack;
}
public static Modpack readMultiMCModpackManifest(File f) throws IOException {
try (ZipFile zipFile = new ZipFile(f)) {
ZipArchiveEntry firstEntry = zipFile.getEntries().nextElement();
@@ -260,7 +269,8 @@ public final class MultiMCInstanceConfiguration {
ZipArchiveEntry entry = zipFile.getEntry(name + "/instance.cfg");
if (entry == null)
throw new IOException("`instance.cfg` not found, " + f + " is not a valid MultiMC modpack.");
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, zipFile.getInputStream(entry));
MultiMCManifest manifest = MultiMCManifest.readMultiMCModpackManifest(f);
MultiMCInstanceConfiguration cfg = new MultiMCInstanceConfiguration(name, zipFile.getInputStream(entry), manifest);
return new Modpack(cfg.getName(), "", "", cfg.getGameVersion(), cfg.getNotes(), cfg);
}
}
@@ -0,0 +1,152 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.mod;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.util.List;
@Immutable
public final class MultiMCManifest {
@SerializedName("formatVersion")
private final int formatVersion;
@SerializedName("components")
private final List<MultiMCManifestComponent> components;
public MultiMCManifest(int formatVersion, List<MultiMCManifestComponent> components) {
this.formatVersion = formatVersion;
this.components = components;
}
public int getFormatVersion() {
return formatVersion;
}
public List<MultiMCManifestComponent> getComponents() {
return components;
}
public static MultiMCManifest readMultiMCModpackManifest(File f) throws IOException {
try (ZipFile zipFile = new ZipFile(f)) {
ZipArchiveEntry firstEntry = zipFile.getEntries().nextElement();
String name = StringUtils.substringBefore(firstEntry.getName(), '/');
ZipArchiveEntry entry = zipFile.getEntry(name + "/mmc-pack.json");
if (entry == null)
return null;
String json = IOUtils.readFullyAsString(zipFile.getInputStream(entry));
MultiMCManifest manifest = JsonUtils.fromNonNullJson(json, MultiMCManifest.class);
if (manifest != null && manifest.getComponents() == null)
throw new IOException("mmc-pack.json malformed.");
return manifest;
}
}
public static final class MultiMCManifestCachedRequires {
@SerializedName("equals")
private final String equalsVersion;
@SerializedName("uid")
private final String uid;
@SerializedName("suggests")
private final String suggests;
public MultiMCManifestCachedRequires(String equalsVersion, String uid, String suggests) {
this.equalsVersion = equalsVersion;
this.uid = uid;
this.suggests = suggests;
}
public String getEqualsVersion() {
return equalsVersion;
}
public String getUid() {
return uid;
}
public String getSuggests() {
return suggests;
}
}
public static final class MultiMCManifestComponent {
@SerializedName("cachedName")
private final String cachedName;
@SerializedName("cachedRequires")
private final List<MultiMCManifestCachedRequires> cachedRequires;
@SerializedName("cachedVersion")
private final String cachedVersion;
@SerializedName("important")
private final boolean important;
@SerializedName("uid")
private final String uid;
@SerializedName("version")
private final String version;
public MultiMCManifestComponent(String cachedName, List<MultiMCManifestCachedRequires> cachedRequires, String cachedVersion, boolean important, String uid, String version) {
this.cachedName = cachedName;
this.cachedRequires = cachedRequires;
this.cachedVersion = cachedVersion;
this.important = important;
this.uid = uid;
this.version = version;
}
public String getCachedName() {
return cachedName;
}
public List<MultiMCManifestCachedRequires> getCachedRequires() {
return cachedRequires;
}
public String getCachedVersion() {
return cachedVersion;
}
public boolean isImportant() {
return important;
}
public String getUid() {
return uid;
}
public String getVersion() {
return version;
}
}
}
@@ -22,6 +22,7 @@ import com.google.gson.reflect.TypeToken;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipFile;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.GameBuilder;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.game.Arguments;
import org.jackhuang.hmcl.game.DefaultGameRepository;
@@ -37,6 +38,7 @@ import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
*
@@ -61,7 +63,24 @@ public final class MultiMCModpackInstallTask extends Task {
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
throw new IllegalArgumentException("Version " + name + " already exists.");
dependents.add(dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion()).buildAsync());
GameBuilder builder = dependencyManager.gameBuilder().name(name).gameVersion(manifest.getGameVersion());
if (manifest.getMmcPack() != null) {
Optional<MultiMCManifest.MultiMCManifestComponent> forge = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("net.minecraftforge")).findAny();
forge.ifPresent(c -> {
if (c.getVersion() != null)
builder.version("forge", c.getVersion());
});
Optional<MultiMCManifest.MultiMCManifestComponent> liteLoader = manifest.getMmcPack().getComponents().stream().filter(e -> e.getUid().equals("com.mumfrey.liteloader")).findAny();
liteLoader.ifPresent(c -> {
if (c.getVersion() != null)
builder.version("liteloader", c.getVersion());
});
}
dependents.add(builder.buildAsync());
onDone().register(event -> {
if (event.isFailed())
repository.removeVersionFromDisk(name);
+41 -12
View File
@@ -5,16 +5,25 @@ GPL v3, see http://www.gnu.org/licenses/gpl.html
HMCL is a Minecraft launcher which supports Mod management, game customizing, auto installing(Forge, LiteLoader and OptiFine), modpack creating, UI customizing and so on.
## Downloads
Binary version can be found here: https://ci.huangyuhui.net/job/HMCL
Archives in GitHub releases are built by closed CIs, uploaded here for backup.
## Contribution
If you want to submit a pull request, there're some requirements:
* IDE: Intellij IDEA.
* Compiler: Java 1.8.
* Do NOT modify `gradle` files.
## HMCLCore
Now HMCLCore is independent and you can use HMCLCore as a library to launch your game.
### GameRepository
Create a game repository `repository` to manage a minecraft installation. Like this.
```java
DefaultGameRepository repository = new DefaultGameRepository(new File(".minecraft").getAbsoluteFile());
@@ -35,17 +44,23 @@ DefaultLauncher launcher = new DefaultLauncher(
.logIn(), // account
// or new AccountBuilder.Builder()
// .setUsername("someone@xxx.com")
// .setPassword("123")
// .create(YggdrasilAccountFactory.INSTANCE)
// .setPassword("someone's password")
// // for Mojang account
// .create(new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE))
// // for Authlib Injector account
// .create(new AuthlibInjectorAccountFactory(
// new AuthlibInjectorDownloader(new File("path to save executables of authlib injector"),
// () -> MojangYggdrasilProvider.INSTANCE)::getArtifactInfo,
// () -> AuthlibInjectorServer.fetchServerInfo("Your authlib injector auth server")))
// .logIn()
new LaunchOptions.Builder()
.setGameDir(repository.getBaseDirectory())
.setGameDir(repository.getBaseDirectory()) // directory that the game saves settings to
.setMaxMemory(...)
.setJava(...)
.setJavaArgs(...)
.setMinecraftArgs(...)
.setHeight(...)
.setWidth(...)
.setJava(...) // executable of JVM
.setJavaArgs(...) // additional Java VM arguments
.setMinecraftArgs(...) // additional Minecraft arguments
.setHeight(...) // height of game window
.setWidth(...) // width of game window
...
.create(),
new ProcessListener() { // listening the process state.
@@ -68,23 +83,37 @@ If you want the command line, just call `launcher.getRawCommandLine`. Also, `Str
### Downloading
HMCLCore just owns a simple way to download a new game.
```java
DefaultDependencyManager dependency = new DefaultDependencyManager(repository, MojangDownloadProvider.INSTANCE, proxy);
DefaultDependencyManager dependency = new DefaultDependencyManager(repository, new MojangDownloadProvider(), proxy);
```
`repository` is your `GameRepository`. `MojangDownloadProvider.INSTANCE` means that we download files from mojang servers. If you want BMCLAPI, `BMCLAPIDownloadProvider.INSTANCE` is just for you. `proxy` is `java.net.Proxy`, if you have a proxy, put it here, or `Proxy.NO_PROXY`.
Now `GameBuilder` can build a game.
```
```java
Task gameBuildingTask = dependency.gameBuilder()
.name("test")
.gameVersion("1.12") // Minecraft version
.version("forge", "14.21.1.2426") // Forge version
.version("liteloader", "1.12-SNAPSHOT-4") // LiteLoader version
.version("optifine", "HD_U_C4") // OptiFine version
.buildAsync()
.buildAsync();
```
Nowadays HMCLCore only supports Forge, LiteLoader and OptiFine auto-installing.
`buildAsync` will return a `Task`, you can call `Task.executor()::start` or simply `Task::start` to start this task. If you want to monitor the execution of tasks, you should see `TaskExecutor` and `Task::executor`.
### Modpack installing
HMCLCore supports Curse, MultiMC modpack.
```java
// Installing curse modpack
new CurseInstallTask(dependency, modpackZipFile, CurseManifest.readCurseForgeModpackManifest(modpackZipFile), "name of the new game");
// Installing MultiMC modpack
new MultiMCModpackInstallTask(dependency, modpackZipFile, MultiMCInstanceConfiguration.readMultiMCModpackManifest(modpackZipFile), "name of the new game");
// ** IMPORTANT **: You should read game settings from MultiMCInstanceConfiguration
```
## HMCL
JavaFX version of HMCL does not support old APIs.
No plugin API.