Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd38c9424f | ||
|
|
c47dea94e3 | ||
|
|
69c0382bd5 | ||
|
|
1d80cd9fa2 | ||
|
|
3721842c8b | ||
|
|
47d8b59b93 | ||
|
|
b5fbc69ee6 | ||
|
|
9d95d3ea66 | ||
|
|
06b3ec8337 | ||
|
|
fabb6b50b4 | ||
|
|
ca627b8a2d | ||
|
|
a0c619a747 | ||
|
|
5386ae3736 | ||
|
|
32fd125b97 | ||
|
|
7a86881926 | ||
|
|
b255fd4601 | ||
|
|
562dd8d916 | ||
|
|
97d820446a | ||
|
|
826d382ad5 | ||
|
|
59d7ac3c82 | ||
|
|
566aed4ed6 | ||
|
|
1b90e94615 | ||
|
|
22ede84c3d | ||
|
|
0c4da069b7 | ||
|
|
90733c6a7e | ||
|
|
44daf656b5 | ||
|
|
90f416095e | ||
|
|
076d4d5dce | ||
|
|
3c77f249e5 | ||
|
|
11e5dc3782 |
No files matched your search
@@ -1,28 +0,0 @@
|
||||
# 公告
|
||||
|
||||
提交前请确保你的启动器版本是**最新的开发版**,可以在启动器设置中更换更新通道至开发版获取更新。
|
||||
|
||||
**如果你在阅读完本公告后仍然希望在此处发布 issue,请将公告内容删去,并按照下面的模板填入相关信息。**
|
||||
|
||||
|
||||
|
||||
我们发现,对于不经常检查邮箱的人来说,GitHub Issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 HMCL 3 用户 QQ 群:219177735 中反馈信息,**而不在 GitHub Issues 中反馈问题**。
|
||||
|
||||
如果你希望在 QQ 群中反馈问题,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
|
||||
|
||||
|
||||
|
||||
# 问题提交
|
||||
|
||||
*完整地填下面的问题提交表对我们很重要,这可以加快我们分析问题原因的速度。*
|
||||
|
||||
* 启动器版本:
|
||||
* 操作系统:
|
||||
* Java 版本:
|
||||
* 错误截图(最好请将整个电脑屏幕的截图发上来):
|
||||
* 游戏版本(如果是启动通过启动器自带的自动安装功能安装的游戏):
|
||||
* 对游戏做的修改(是否自行通过安装器安装 Rift 等 API,以及 mod):
|
||||
* 游戏崩溃报告(如果有):
|
||||
* 启动器崩溃报告(如果有):
|
||||
* 启动器日志文件(出现问题后点击启动器设置中的【导出启动器日志】按钮):
|
||||
* 问题描述(如何触发问题):
|
||||
+5
-24
@@ -4,33 +4,14 @@
|
||||
hs_err_pid*
|
||||
|
||||
.gradle
|
||||
.nb-gradle
|
||||
|
||||
*.lck
|
||||
*.1
|
||||
*.2
|
||||
*.log
|
||||
.mine*
|
||||
NVIDIA
|
||||
*.json
|
||||
*.hmd
|
||||
|
||||
# gradle build
|
||||
/build/
|
||||
/HMCL/build/
|
||||
/HMCLCore/build/
|
||||
|
||||
# idea
|
||||
.idea
|
||||
/out/
|
||||
/HMCL/out/
|
||||
/HMCLCore/out/
|
||||
|
||||
# eclipse
|
||||
/bin/
|
||||
/HMCL/bin/
|
||||
/HMCLCore/bin/
|
||||
.classpath
|
||||
.project
|
||||
.settings
|
||||
|
||||
# netbeans
|
||||
.nb-gradle
|
||||
/HMCLaF/build/
|
||||
/HMCLAPI/build/
|
||||
/HMCLCore/build/
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<gradle-project-properties>
|
||||
<!--DO NOT EDIT THIS FILE! - Used by the Gradle plugin of NetBeans.-->
|
||||
<target-platform-name>j2se</target-platform-name>
|
||||
<target-platform>1.8</target-platform>
|
||||
<source-level>1.8</source-level>
|
||||
<common-tasks>
|
||||
<task>
|
||||
<display-name>launch4j</display-name>
|
||||
<non-blocking>no</non-blocking>
|
||||
<task-names>
|
||||
<name must-exist="yes">launch4j</name>
|
||||
</task-names>
|
||||
<task-args/>
|
||||
<task-jvm-args/>
|
||||
</task>
|
||||
<task>
|
||||
<display-name>createApp</display-name>
|
||||
<non-blocking>no</non-blocking>
|
||||
<task-names>
|
||||
<name must-exist="yes">createApp</name>
|
||||
</task-names>
|
||||
<task-args/>
|
||||
<task-jvm-args/>
|
||||
</task>
|
||||
</common-tasks>
|
||||
<script-platform>
|
||||
<spec-name>j2se</spec-name>
|
||||
<spec-version>1.8</spec-version>
|
||||
</script-platform>
|
||||
<license-header>
|
||||
<name>GNU General Public License</name>
|
||||
<template>license-header.txt</template>
|
||||
<property name="organization">huangyuhui</property>
|
||||
</license-header>
|
||||
</gradle-project-properties>
|
||||
File renamed without changes.
Binary file not shown.
Executable
BIN
Binary file not shown.
Regular → Executable
+164
-127
@@ -1,160 +1,197 @@
|
||||
plugins {
|
||||
id 'com.github.johnrengelman.shadow' version '4.0.0'
|
||||
id 'application'
|
||||
}
|
||||
|
||||
import java.nio.file.FileSystems
|
||||
import java.security.KeyFactory
|
||||
import java.security.MessageDigest
|
||||
import java.security.Signature
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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/}.
|
||||
*/
|
||||
import java.util.jar.JarFile
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.jar.Pack200
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import java.security.MessageDigest
|
||||
import java.util.jar.JarOutputStream
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipFile
|
||||
import java.nio.file.Files
|
||||
|
||||
import org.tukaani.xz.LZMA2Options
|
||||
import org.tukaani.xz.XZOutputStream
|
||||
plugins {
|
||||
id "edu.sc.seis.macAppBundle" version "2.1.8"
|
||||
id "me.tatarka.retrolambda" version "3.7.0"
|
||||
id 'edu.sc.seis.launch4j' version '2.4.2'
|
||||
//id "org.jetbrains.kotlin.jvm" version "1.1.2-2"
|
||||
}
|
||||
|
||||
def buildnumber = System.getenv("BUILD_NUMBER") ?: "SNAPSHOT"
|
||||
if (System.getenv("BUILD_NUMBER") != null && System.getenv("BUILD_NUMBER_OFFSET") != null)
|
||||
buildnumber = (Integer.parseInt(System.getenv("BUILD_NUMBER")) - Integer.parseInt(System.getenv("BUILD_NUMBER_OFFSET"))).toString()
|
||||
def versionroot = System.getenv("VERSION_ROOT") ?: "3.2"
|
||||
version = versionroot + '.' + buildnumber
|
||||
if (!hasProperty('mainClass')) {
|
||||
ext.mainClass = 'org.jackhuang.hmcl.Main'
|
||||
}
|
||||
|
||||
mainClassName = 'org.jackhuang.hmcl.Main'
|
||||
def buildnumber = System.getenv("TRAVIS_BUILD_NUMBER")
|
||||
if (buildnumber == null)
|
||||
buildnumber = System.getenv("BUILD_NUMBER")
|
||||
if (buildnumber == null)
|
||||
buildnumber = "60"
|
||||
|
||||
def versionroot = System.getenv("VERSION_ROOT")
|
||||
if (versionroot == null)
|
||||
versionroot = "2.7.10"
|
||||
|
||||
String mavenGroupId = 'HMCL'
|
||||
String mavenVersion = versionroot + '.' + buildnumber
|
||||
String bundleName = "Hello Minecraft! Launcher"
|
||||
|
||||
group = mavenGroupId
|
||||
version = mavenVersion
|
||||
|
||||
String mavenArtifactId = name
|
||||
|
||||
task generateSources(type: Sync) {
|
||||
from 'src/main/java'
|
||||
into "$buildDir/generated-src"
|
||||
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [
|
||||
'HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING': mavenVersion
|
||||
])
|
||||
}
|
||||
compileJava.setSource "$buildDir/generated-src"
|
||||
compileJava.dependsOn generateSources
|
||||
|
||||
configurations {
|
||||
coreCompile.extendsFrom compile
|
||||
coreRuntime.extendsFrom runtime
|
||||
}
|
||||
|
||||
configure(install.repositories.mavenInstaller) {
|
||||
pom.project {
|
||||
groupId = mavenGroupId
|
||||
artifactId = mavenArtifactId
|
||||
version = mavenVersion
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(":HMCLaF")
|
||||
compile project(":HMCLAPI")
|
||||
compile project(":HMCLCore")
|
||||
compile rootProject.files("lib/JFoenix.jar")
|
||||
}
|
||||
|
||||
def digest(String algorithm, byte[] bytes) {
|
||||
return MessageDigest.getInstance(algorithm).digest(bytes)
|
||||
}
|
||||
|
||||
def createChecksum(File file) {
|
||||
def algorithm = "SHA-1"
|
||||
def suffix = "sha1"
|
||||
new File(file.parentFile, file.name + "." + suffix).text = digest(algorithm, file.bytes).encodeHex().toString() + "\n"
|
||||
}
|
||||
|
||||
def attachSignature(File jar) {
|
||||
def keyLocation = System.getenv("HMCL_SIGNATURE_KEY");
|
||||
if (keyLocation == null)
|
||||
return
|
||||
def privatekey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(new File(keyLocation).bytes))
|
||||
|
||||
def signer = Signature.getInstance("SHA512withRSA")
|
||||
signer.initSign(privatekey)
|
||||
new ZipFile(jar).withCloseable { zip ->
|
||||
zip.stream()
|
||||
.sorted(Comparator.comparing { it.name })
|
||||
.filter { it.name != "META-INF/hmcl_signature" }
|
||||
.forEach {
|
||||
signer.update(digest("SHA-512", it.name.getBytes("UTF-8")))
|
||||
signer.update(digest("SHA-512", zip.getInputStream(it).bytes))
|
||||
}
|
||||
}
|
||||
def signature = signer.sign()
|
||||
|
||||
FileSystems.newFileSystem(URI.create("jar:" + jar.toURI()), [:]).withCloseable { zipfs ->
|
||||
Files.newOutputStream(zipfs.getPath("META-INF/hmcl_signature")).withCloseable { it << signature }
|
||||
}
|
||||
}
|
||||
|
||||
ext.packer = Pack200.newPacker()
|
||||
packer.properties()["pack.effort"] = "9"
|
||||
ext.unpacker = Pack200.newUnpacker()
|
||||
|
||||
def repack(File file) {
|
||||
def packed = new ByteArrayOutputStream()
|
||||
new JarFile(file).withCloseable { packer.pack(it, packed) }
|
||||
new JarOutputStream(file.newOutputStream()).withCloseable { unpacker.unpack(new ByteArrayInputStream(packed.toByteArray()), it) }
|
||||
retrolambda {
|
||||
javaVersion = JavaVersion.VERSION_1_7
|
||||
}
|
||||
|
||||
jar {
|
||||
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
|
||||
|
||||
manifest {
|
||||
attributes 'Created-By': 'Copyright(c) 2013-2018 huangyuhui.',
|
||||
'Main-Class': mainClassName,
|
||||
'Multi-Release': 'true',
|
||||
'Implementation-Version': version
|
||||
attributes 'Created-By' : 'Copyright(c) 2013-2017 huangyuhui.',
|
||||
'Main-Class' : mainClass
|
||||
}
|
||||
finalizedBy shadowJar
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
classifier = null
|
||||
|
||||
exclude 'META-INF/maven/**'
|
||||
exclude 'META-INF/NOTICE.txt'
|
||||
exclude 'META-INF/LICENSE.txt'
|
||||
|
||||
dependencies {
|
||||
exclude(dependency('org.jetbrains:annotations'))
|
||||
}
|
||||
|
||||
|
||||
doLast {
|
||||
repack(jar.archivePath)
|
||||
attachSignature(jar.archivePath)
|
||||
createChecksum(jar.archivePath)
|
||||
new File("build/signed").mkdirs()
|
||||
ant.signjar(signedjar: archivePath, jar: archivePath,
|
||||
keystore: "HMCL.keystore", storepass: "123456",
|
||||
alias: "HMCL")
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
archivePath.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
messageDigest.update(buf, 0, bytesRead);
|
||||
}
|
||||
def sha1Hex = new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0')
|
||||
def fileEx = new File(project.buildDir, "libs/" + archivePath.getName() + ".sha1")
|
||||
if (!fileEx.exists()) fileEx.createNewFile()
|
||||
fileEx.append sha1Hex
|
||||
}
|
||||
}
|
||||
|
||||
def createExecutable(String suffix, String header) {
|
||||
def output = new File(jar.archivePath.parentFile, jar.archivePath.name[0..-4] + suffix)
|
||||
output.bytes = new File(project.projectDir, header).bytes
|
||||
output << jar.archivePath.bytes
|
||||
createChecksum(output)
|
||||
launch4j {
|
||||
supportUrl = 'http://www.mcbbs.net/thread-142335-1-1.html'
|
||||
jreMinVersion = '1.7.0'
|
||||
|
||||
mainClassName = mainClass
|
||||
icon = new File(project.buildDir, '../icon.ico').absolutePath
|
||||
version = mavenVersion
|
||||
downloadUrl = 'http://java.com/download'
|
||||
copyright = "Copyright(c) 2013-2017 huangyuhui."
|
||||
|
||||
jar = new File(project.buildDir, 'libs/' + mavenGroupId + '-' + mavenVersion + '.jar').absolutePath
|
||||
outfile = mavenGroupId + '-' + mavenVersion + '.exe'
|
||||
messagesJreVersionError = 'This application requires a Java Runtime Environment installation, or the runtime is corrupted.\n\u6ca1\u6709\u627e\u5230\u004a\u0061\u0076\u0061\u8fd0\u884c\u65f6\uff0c\u8bf7\u4e0d\u8981\u4f7f\u7528\u7eff\u8272\u004a\u0061\u0076\u0061\uff0c\u8bf7\u4f7f\u7528\u5b89\u88c5\u7248\u7684\u004a\u0061\u0076\u0061\uff0c\u70b9\u51fb\u786e\u5b9a\u8fdb\u5165\u004a\u0061\u0076\u0061\u5b89\u88c5\u9875\u9762\u3002'
|
||||
}
|
||||
|
||||
processResources {
|
||||
ext.convertToBSS = { String resource ->
|
||||
exclude resource
|
||||
doFirst {
|
||||
def cssFile = new File(this.projectDir, "src/main/resources/" + resource)
|
||||
def bssFile = new File(this.projectDir, "build/compiled-resources/" + resource[0..-4] + "bss")
|
||||
bssFile.parentFile.mkdirs()
|
||||
exec {
|
||||
commandLine 'javapackager', '-createbss', '-outdir', bssFile.parent, '-srcfiles', cssFile.path
|
||||
}
|
||||
}
|
||||
}
|
||||
from "build/compiled-resources"
|
||||
|
||||
convertToBSS "assets/css/root.css"
|
||||
convertToBSS "assets/css/blue.css"
|
||||
}
|
||||
|
||||
task makePack(dependsOn: jar) {
|
||||
ext.outputPath = new File(jar.archivePath.parentFile, jar.archivePath.name[0..-4] + "pack")
|
||||
doLast {
|
||||
outputPath.newOutputStream().withCloseable { out ->
|
||||
new JarFile(jar.archivePath).withCloseable { jarFile -> packer.pack(jarFile, out) }
|
||||
}
|
||||
createChecksum(outputPath)
|
||||
from(sourceSets.main.resources.srcDirs) {
|
||||
exclude 'icon.icns'
|
||||
}
|
||||
}
|
||||
|
||||
task makePackXz(dependsOn: makePack) doLast {
|
||||
def packXz = new File(makePack.outputPath.parentFile, makePack.outputPath.name + ".xz")
|
||||
new XZOutputStream(packXz.newOutputStream(), new LZMA2Options(6)).withCloseable { it << makePack.outputPath.bytes }
|
||||
createChecksum(packXz)
|
||||
task makeExecutable(dependsOn: jar) doLast {
|
||||
ext {
|
||||
jar.classifier = ''
|
||||
makeExecutableinjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
makeExecutableoutjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
}
|
||||
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".exe")
|
||||
def fos = new FileOutputStream(loc)
|
||||
def is = new FileInputStream(new File(project.buildDir, '../HMCLauncher.exe'))
|
||||
int read
|
||||
def bytes = new byte[8192]
|
||||
while((read = is.read(bytes)) != -1)
|
||||
fos.write(bytes, 0, read);
|
||||
is.close()
|
||||
is = new FileInputStream(makeExecutableinjar)
|
||||
while((read = is.read(bytes)) != -1)
|
||||
fos.write(bytes, 0, read);
|
||||
is.close()
|
||||
fos.close()
|
||||
|
||||
}
|
||||
|
||||
task makePackGz(dependsOn: makePack) doLast {
|
||||
def packGz = new File(makePack.outputPath.parentFile, makePack.outputPath.name + ".gz")
|
||||
new GZIPOutputStream(packGz.newOutputStream()).withCloseable { it << makePack.outputPath.bytes }
|
||||
createChecksum(packGz)
|
||||
task makePackGZ(dependsOn: jar) doLast {
|
||||
ext {
|
||||
jar.classifier = ''
|
||||
makeExecutableinjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
makeExecutableoutjar = jar.archivePath
|
||||
jar.classifier = ''
|
||||
}
|
||||
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz")
|
||||
def os = new GZIPOutputStream(new FileOutputStream(loc))
|
||||
Pack200.newPacker().pack new JarFile(makeExecutableinjar), os
|
||||
os.close()
|
||||
|
||||
def messageDigest = MessageDigest.getInstance("SHA1")
|
||||
loc.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
|
||||
messageDigest.update(buf, 0, bytesRead);
|
||||
}
|
||||
def sha1Hex = new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0')
|
||||
def fileEx = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz.sha1")
|
||||
if (!fileEx.exists()) fileEx.createNewFile()
|
||||
fileEx.append sha1Hex
|
||||
}
|
||||
|
||||
|
||||
task makeExecutables(dependsOn: jar) doLast {
|
||||
createExecutable("exe", "src/main/resources/assets/HMCLauncher.exe")
|
||||
task macAppCompressed(type: Zip, dependsOn: createApp) doLast {
|
||||
archiveName "HMCL-$mavenVersion-MacOSApp.zip"
|
||||
include '**'
|
||||
destinationDir file("$buildDir/libs/")
|
||||
from "$buildDir/macApp"
|
||||
}
|
||||
|
||||
build.dependsOn makePackXz
|
||||
build.dependsOn makePackGz
|
||||
build.dependsOn makeExecutables
|
||||
macAppBundle {
|
||||
mainClassName = mainClass
|
||||
icon = "src/main/icon.icns"
|
||||
javaProperties.put("apple.laf.useScreenMenuBar", "true")
|
||||
}
|
||||
|
||||
build.dependsOn makeExecutable
|
||||
build.dependsOn makePackGZ
|
||||
build.dependsOn macAppCompressed
|
||||
Regular → Executable
File renamed without changes.
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -1,2 +0,0 @@
|
||||
rootProject.name = 'HMCL'
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import javafx.application.Application;
|
||||
import javafx.application.Platform;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.setting.ConfigHolder;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Launcher extends Application {
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) {
|
||||
Thread.currentThread().setUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
|
||||
try {
|
||||
try {
|
||||
ConfigHolder.init();
|
||||
} catch (IOException e) {
|
||||
Main.showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
|
||||
}
|
||||
|
||||
// runLater to ensure ConfigHolder.init() finished initialization
|
||||
Platform.runLater(() -> {
|
||||
// When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false,
|
||||
// Stage.show() cannot work again because JavaFX Toolkit have already shut down.
|
||||
Platform.setImplicitExit(false);
|
||||
Controllers.initialize(primaryStage);
|
||||
primaryStage.setResizable(false);
|
||||
primaryStage.setScene(Controllers.getScene());
|
||||
|
||||
UpdateChecker.init();
|
||||
|
||||
primaryStage.show();
|
||||
});
|
||||
} catch (Throwable e) {
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
|
||||
|
||||
try {
|
||||
LOG.info("*** " + Metadata.TITLE + " ***");
|
||||
LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
|
||||
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
|
||||
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
|
||||
LOG.info("Java Home: " + System.getProperty("java.home"));
|
||||
LOG.info("Current Directory: " + Paths.get("").toAbsolutePath());
|
||||
LOG.info("HMCL Directory: " + Metadata.HMCL_DIRECTORY);
|
||||
LOG.info("Memory: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB");
|
||||
ManagementFactory.getMemoryPoolMXBeans().stream().filter(bean -> bean.getName().equals("Metaspace")).findAny()
|
||||
.ifPresent(bean -> LOG.info("Metaspace: " + bean.getUsage().getUsed() / 1024 / 1024 + "MB"));
|
||||
|
||||
launch(args);
|
||||
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
|
||||
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopApplication() {
|
||||
LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
return;
|
||||
Controllers.getStage().close();
|
||||
Schedulers.shutdown();
|
||||
Controllers.shutdown();
|
||||
Platform.exit();
|
||||
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
|
||||
});
|
||||
}
|
||||
|
||||
public static void stopWithoutPlatform() {
|
||||
LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
|
||||
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (Controllers.getStage() == null)
|
||||
return;
|
||||
Controllers.getStage().close();
|
||||
Schedulers.shutdown();
|
||||
Controllers.shutdown();
|
||||
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
|
||||
});
|
||||
}
|
||||
|
||||
public static List<File> getCurrentJarFiles() {
|
||||
List<File> result = new LinkedList<>();
|
||||
if (Launcher.class.getClassLoader() instanceof URLClassLoader) {
|
||||
URL[] urls = ((URLClassLoader) Launcher.class.getClassLoader()).getURLs();
|
||||
for (URL u : urls)
|
||||
try {
|
||||
File f = new File(u.toURI());
|
||||
if (f.isFile() && (f.getName().endsWith(".exe") || f.getName().endsWith(".jar")))
|
||||
result.add(f);
|
||||
} catch (URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (result.isEmpty())
|
||||
return null;
|
||||
else
|
||||
return result;
|
||||
}
|
||||
|
||||
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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
|
||||
@@ -13,106 +13,239 @@
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.swing.*;
|
||||
import java.io.File;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Authenticator;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.RepaintManager;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.api.ILogger;
|
||||
import org.jackhuang.hmcl.api.PluginManager;
|
||||
import org.jackhuang.hmcl.api.VersionNumber;
|
||||
import org.jackhuang.hmcl.core.MCUtils;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.ui.LogWindow;
|
||||
import org.jackhuang.hmcl.ui.MainFrame;
|
||||
import org.jackhuang.hmcl.util.CrashReporter;
|
||||
import org.jackhuang.hmcl.util.DefaultPlugin;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.util.lang.SupportedLocales;
|
||||
import org.jackhuang.hmcl.util.log.Configuration;
|
||||
import org.jackhuang.hmcl.util.log.appender.ConsoleAppender;
|
||||
import org.jackhuang.hmcl.util.log.layout.DefaultLayout;
|
||||
import org.jackhuang.hmcl.util.ui.MyRepaintManager;
|
||||
import org.jackhuang.hmcl.util.upgrade.IUpgrader;
|
||||
import org.jackhuang.hmcl.laf.BeautyEyeLNFHelper;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.setProperty("java.net.useSystemProxies", "true");
|
||||
System.setProperty("http.agent", "HMCL/" + Metadata.VERSION);
|
||||
System.setProperty("javafx.autoproxy.disable", "true");
|
||||
|
||||
checkJavaFX();
|
||||
checkDirectoryPath();
|
||||
|
||||
// This environment check will take ~300ms
|
||||
thread(() -> checkDSTRootCAX3(), "CA Certificate Check", true);
|
||||
|
||||
Logging.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
|
||||
|
||||
if (UpdateHandler.processArguments(args)) {
|
||||
return;
|
||||
private static final X509TrustManager XTM = new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
Launcher.main(args);
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
};
|
||||
private static final HostnameVerifier HNV = (hostname, session) -> true;
|
||||
|
||||
public static final String LAUNCHER_NAME = "Hello Minecraft! Launcher";
|
||||
public static final String LAUNCHER_VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@";
|
||||
public static final int MINIMUM_LAUNCHER_VERSION = 16;
|
||||
|
||||
/**
|
||||
* Make the main window title.
|
||||
*
|
||||
* @return the MainWindow title.
|
||||
*/
|
||||
public static String makeTitle() {
|
||||
return LAUNCHER_NAME + ' ' + LAUNCHER_VERSION;
|
||||
}
|
||||
|
||||
private static void checkDirectoryPath() {
|
||||
String currentDirectory = new File("").getAbsolutePath();
|
||||
if (currentDirectory.contains("!")) {
|
||||
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
|
||||
showErrorAndExit("Exclamation mark(!) is not allowed in the path where HMCL is in.\n"
|
||||
+ "The path is " + currentDirectory);
|
||||
}
|
||||
public static String shortTitle() {
|
||||
return "HMCL" + ' ' + LAUNCHER_VERSION;
|
||||
}
|
||||
|
||||
private static void checkJavaFX() {
|
||||
try {
|
||||
Class.forName("javafx.application.Application");
|
||||
} catch (ClassNotFoundException e) {
|
||||
showErrorAndExit(i18n("fatal.missing_javafx"));
|
||||
}
|
||||
}
|
||||
public static final Logger LOGGER = Logger.getLogger(Main.class.getName());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
public static void main(String[] args) throws IOException {
|
||||
{
|
||||
if (new File("").getAbsolutePath().contains("!")) {
|
||||
MessageBox.show("HMCL cannot run at such path that contains '!'");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
File file = new File("hmcl.log").getAbsoluteFile();
|
||||
if (!file.exists() && !file.createNewFile())
|
||||
LOGGER.log(Level.WARNING, "Failed to create log file {0}", file);
|
||||
Configuration.DEFAULT.appenders.add(new ConsoleAppender("File", new DefaultLayout(), true, new FileOutputStream(file), true));
|
||||
} catch (IOException ex) {
|
||||
LOGGER.log(Level.SEVERE, "Failed to add log appender File because an error occurred while creating or opening hmcl.log", ex);
|
||||
}
|
||||
|
||||
org.jackhuang.hmcl.util.log.logger.Logger logger = new org.jackhuang.hmcl.util.log.logger.Logger("HMCL");
|
||||
HMCLog.LOGGER = new ILogger() {
|
||||
@Override
|
||||
public void log(String msg) {
|
||||
logger.info(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg) {
|
||||
logger.warn(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(String msg, Throwable t) {
|
||||
logger.warn(msg, t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void err(String msg) {
|
||||
logger.error(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void err(String msg, Throwable t) {
|
||||
logger.error(msg, t);
|
||||
}
|
||||
};
|
||||
|
||||
HMCLApi.HMCL_VERSION = VersionNumber.asVersion(LAUNCHER_VERSION);
|
||||
|
||||
PluginManager.getPlugin(DefaultPlugin.class);
|
||||
for (String s : args)
|
||||
if (s.startsWith("--plugin=")) {
|
||||
String c = s.substring("--plugin=".length());
|
||||
try {
|
||||
PluginManager.getPlugin(Class.forName(c));
|
||||
} catch (ClassNotFoundException ex) {
|
||||
HMCLog.warn("Class: " + c + " not found, please add your plugin jar to class path.", ex);
|
||||
}
|
||||
} else if (s.startsWith("--help")) {
|
||||
System.out.println("HMCL command line help");
|
||||
System.out.println("--noupdate: this arg will prevent HMCL from initializing the newest app version in %appdata%/.hmcl");
|
||||
System.out.println("--plugin=<your plugin class>: this arg will allow a new plugin to be loaded, please keep your jar in system class path and this class extends IPlugin.");
|
||||
return;
|
||||
}
|
||||
PluginManager.loadPlugins();
|
||||
|
||||
IUpgrader.NOW_UPGRADER.parseArguments(HMCLApi.HMCL_VERSION, Arrays.asList(args));
|
||||
|
||||
System.setProperty("awt.useSystemAAFontSettings", "on");
|
||||
System.setProperty("swing.aatext", "true");
|
||||
System.setProperty("sun.java2d.noddraw", "true");
|
||||
System.setProperty("sun.java2d.dpiaware", "false");
|
||||
System.setProperty("https.protocols", "SSLv3,TLSv1");
|
||||
|
||||
try {
|
||||
SSLContext c = SSLContext.getInstance("SSL");
|
||||
c.init(null, new X509TrustManager[] { XTM }, new java.security.SecureRandom());
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(c.getSocketFactory());
|
||||
} catch (GeneralSecurityException ignore) {
|
||||
}
|
||||
HttpsURLConnection.setDefaultHostnameVerifier(HNV);
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler(new CrashReporter(true));
|
||||
|
||||
HMCLog.log("*** " + Main.makeTitle() + " ***");
|
||||
|
||||
String s = Settings.getInstance().getLocalization();
|
||||
for (SupportedLocales sl : SupportedLocales.values())
|
||||
if (sl.name().equals(s)) {
|
||||
SupportedLocales.setNowLocale(sl);
|
||||
Locale.setDefault(sl.self);
|
||||
JOptionPane.setDefaultLocale(sl.self);
|
||||
}
|
||||
|
||||
if (System.getProperty("java.vm.name").contains("Open")) // OpenJDK
|
||||
MessageBox.showLocalized("ui.message.open_jdk");
|
||||
|
||||
try {
|
||||
BeautyEyeLNFHelper.launchBeautyEyeLNF();
|
||||
RepaintManager.setCurrentManager(new MyRepaintManager());
|
||||
} catch (Exception ex) {
|
||||
HMCLog.warn("Failed to set look and feel...", ex);
|
||||
}
|
||||
|
||||
LogWindow.INSTANCE.clean();
|
||||
|
||||
Settings.UPDATE_CHECKER.upgrade.register(IUpgrader.NOW_UPGRADER);
|
||||
Settings.UPDATE_CHECKER.process(false).reg(t -> Main.invokeUpdate()).execute();
|
||||
|
||||
if (Settings.getInstance().getProxyType() != Proxy.Type.DIRECT && StrUtils.isNotBlank(Settings.getInstance().getProxyHost())) {
|
||||
switch (Settings.getInstance().getProxyType()) {
|
||||
case DIRECT:
|
||||
break;
|
||||
case HTTP:
|
||||
System.setProperty("http.proxyHost", Settings.getInstance().getProxyHost());
|
||||
if (StrUtils.isNotBlank(Settings.getInstance().getProxyPort()))
|
||||
System.setProperty("http.proxyPort", Settings.getInstance().getProxyPort());
|
||||
HMCLog.log("Initialized HTTP proxy.");
|
||||
break;
|
||||
case SOCKS:
|
||||
System.setProperty("socksProxyHost", Settings.getInstance().getProxyHost());
|
||||
if (StrUtils.isNotBlank(Settings.getInstance().getProxyPort()))
|
||||
System.setProperty("socksProxyPort", Settings.getInstance().getProxyPort());
|
||||
HMCLog.log("Initialized Socks v4/v5 proxy.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (StrUtils.isNotBlank(Settings.getInstance().getProxyUserName()) && StrUtils.isNotBlank(Settings.getInstance().getProxyPassword()))
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(Settings.getInstance().getProxyUserName(), Settings.getInstance().getProxyPassword().toCharArray());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MainFrame.showMainFrame();
|
||||
}
|
||||
showWarningAndContinue(i18n("fatal.missing_dst_root_ca_x3"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that a fatal error has occurred, and that the application cannot start.
|
||||
*/
|
||||
static void showErrorAndExit(String message) {
|
||||
System.err.println(message);
|
||||
System.err.println("A fatal error has occurred, forcibly exiting.");
|
||||
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
|
||||
System.exit(1);
|
||||
public static void invokeUpdate() {
|
||||
MainFrame.INSTANCE.invokeUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates that potential issues have been detected, and that the application may not function properly (but it can still run).
|
||||
*/
|
||||
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);
|
||||
public static ImageIcon getIcon(String path) {
|
||||
try {
|
||||
return new ImageIcon(Main.class.getResource("/org/jackhuang/hmcl/" + path));
|
||||
} catch (Exception e) {
|
||||
HMCLog.err("Failed to load icon", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.jackhuang.hmcl.util.io.JarUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
/**
|
||||
* Stores metadata about this application.
|
||||
*/
|
||||
public final class Metadata {
|
||||
private Metadata() {}
|
||||
|
||||
public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.thisJar().flatMap(JarUtils::getImplementationVersion).orElse("@develop@"));
|
||||
public static final String NAME = "HMCL";
|
||||
public static final String TITLE = NAME + " " + VERSION;
|
||||
|
||||
public static final String UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link");
|
||||
public static final String CONTACT_URL = "https://hmcl.huangyuhui.net/contact";
|
||||
public static final String HELP_URL = "https://hmcl.huangyuhui.net/help";
|
||||
public static final String PUBLISH_URL = "http://www.mcbbs.net/thread-142335-1-1.html";
|
||||
|
||||
public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
|
||||
public static final Path HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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.api.event.config;
|
||||
|
||||
import org.jackhuang.hmcl.api.event.SimpleEvent;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
|
||||
/**
|
||||
* This event gets fired when the selected profile changed.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.api.HMCLApi#EVENT_BUS}
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
* @param Profile the new profile.
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class ProfileChangedEvent extends SimpleEvent<Profile> {
|
||||
|
||||
public ProfileChangedEvent(Object source, Profile value) {
|
||||
super(source, value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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.api.event.config;
|
||||
|
||||
import java.util.EventObject;
|
||||
|
||||
/**
|
||||
* This event gets fired when loading profiles.
|
||||
* <br>
|
||||
* This event is fired on the {@link org.jackhuang.hmcl.api.HMCLApi#EVENT_BUS}
|
||||
* @param source {@link org.jackhuang.hmcl.setting.Settings}
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class ProfileLoadingEvent extends EventObject {
|
||||
|
||||
public ProfileLoadingEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import org.jackhuang.hmcl.download.DefaultCacheRepository;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class HMCLCacheRepository extends DefaultCacheRepository {
|
||||
|
||||
private final StringProperty directory = new SimpleStringProperty();
|
||||
|
||||
public HMCLCacheRepository() {
|
||||
directory.addListener((a, b, t) -> changeDirectory(Paths.get(t)));
|
||||
}
|
||||
|
||||
public String getDirectory() {
|
||||
return directory.get();
|
||||
}
|
||||
|
||||
public StringProperty directoryProperty() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setDirectory(String directory) {
|
||||
this.directory.set(directory);
|
||||
}
|
||||
|
||||
public static final HMCLCacheRepository REPOSITORY = new HMCLCacheRepository();
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.launch.DefaultLauncher;
|
||||
import org.jackhuang.hmcl.launch.ProcessListener;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class HMCLGameLauncher extends DefaultLauncher {
|
||||
|
||||
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) {
|
||||
this(repository, versionId, authInfo, options, null);
|
||||
}
|
||||
|
||||
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
|
||||
this(repository, versionId, authInfo, options, listener, true);
|
||||
}
|
||||
|
||||
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
|
||||
super(repository, versionId, authInfo, options, listener, daemon);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Map<String, String> getConfigurations() {
|
||||
Map<String, String> res = super.getConfigurations();
|
||||
res.put("${launcher_name}", Metadata.NAME);
|
||||
res.put("${launcher_version}", Metadata.VERSION);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class HMCLGameRepository extends DefaultGameRepository {
|
||||
private final Profile profile;
|
||||
private final Map<String, VersionSetting> versionSettings = new HashMap<>();
|
||||
private final Set<String> beingModpackVersions = new HashSet<>();
|
||||
|
||||
public boolean checkedModpack = false, checkingModpack = false;
|
||||
|
||||
public HMCLGameRepository(Profile profile, File baseDirectory) {
|
||||
super(baseDirectory);
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public Profile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getRunDirectory(String id) {
|
||||
if (beingModpackVersions.contains(id) || isModpack(id))
|
||||
return getVersionRoot(id);
|
||||
else {
|
||||
VersionSetting vs = profile.getVersionSetting(id);
|
||||
switch (vs.getGameDirType()) {
|
||||
case VERSION_FOLDER: return getVersionRoot(id);
|
||||
case ROOT_FOLDER: return super.getRunDirectory(id);
|
||||
case CUSTOM: return new File(vs.getGameDir());
|
||||
default: throw new Error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refreshVersionsImpl() {
|
||||
versionSettings.clear();
|
||||
super.refreshVersionsImpl();
|
||||
versions.keySet().forEach(this::loadVersionSetting);
|
||||
|
||||
try {
|
||||
File file = new File(getBaseDirectory(), "launcher_profiles.json");
|
||||
if (!file.exists() && !versions.isEmpty())
|
||||
FileUtils.writeText(file, PROFILE);
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void changeDirectory(File newDirectory) {
|
||||
setBaseDirectory(newDirectory);
|
||||
refreshVersionsAsync().start();
|
||||
}
|
||||
|
||||
private void clean(File directory) throws IOException {
|
||||
FileUtils.deleteDirectory(new File(directory, "crash-reports"));
|
||||
FileUtils.deleteDirectory(new File(directory, "logs"));
|
||||
}
|
||||
|
||||
public void clean(String id) throws IOException {
|
||||
clean(getBaseDirectory());
|
||||
clean(getRunDirectory(id));
|
||||
}
|
||||
|
||||
private File getVersionSettingFile(String id) {
|
||||
return new File(getVersionRoot(id), "hmclversion.cfg");
|
||||
}
|
||||
|
||||
private void loadVersionSetting(String id) {
|
||||
File file = getVersionSettingFile(id);
|
||||
if (file.exists())
|
||||
try {
|
||||
VersionSetting versionSetting = GSON.fromJson(FileUtils.readText(file), VersionSetting.class);
|
||||
initVersionSetting(id, versionSetting);
|
||||
} catch (Exception ex) {
|
||||
// If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated.
|
||||
initVersionSetting(id, new VersionSetting());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new version setting if version id has no version setting.
|
||||
* @param id the version id.
|
||||
* @return new version setting, null if given version does not exist.
|
||||
*/
|
||||
public VersionSetting createVersionSetting(String id) {
|
||||
if (!hasVersion(id))
|
||||
return null;
|
||||
if (versionSettings.containsKey(id))
|
||||
return getVersionSetting(id);
|
||||
else
|
||||
return initVersionSetting(id, new VersionSetting());
|
||||
}
|
||||
|
||||
private VersionSetting initVersionSetting(String id, VersionSetting vs) {
|
||||
vs.addPropertyChangedListener(a -> saveVersionSetting(id));
|
||||
versionSettings.put(id, vs);
|
||||
return vs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version setting for version id.
|
||||
*
|
||||
* @param id version id
|
||||
*
|
||||
* @return may return null if the id not exists
|
||||
*/
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
if (!versionSettings.containsKey(id))
|
||||
loadVersionSetting(id);
|
||||
VersionSetting setting = versionSettings.get(id);
|
||||
if (setting != null && isModpack(id))
|
||||
setting.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||
return setting;
|
||||
}
|
||||
|
||||
public File getVersionIconFile(String id) {
|
||||
return new File(getVersionRoot(id), "icon.png");
|
||||
}
|
||||
|
||||
public Image getVersionIconImage(String id) {
|
||||
if (id == null || !isLoaded())
|
||||
return new Image("/assets/img/grass.png");
|
||||
|
||||
Version version = getVersion(id);
|
||||
File iconFile = getVersionIconFile(id);
|
||||
if (iconFile.exists())
|
||||
return new Image("file:" + iconFile.getAbsolutePath());
|
||||
else if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass()))
|
||||
return new Image("/assets/img/furnace.png");
|
||||
else if ("cpw.mods.modlauncher.Launcher".equals(version.getMainClass()))
|
||||
return new Image("/assets/img/furnace.png");
|
||||
else
|
||||
return new Image("/assets/img/grass.png");
|
||||
}
|
||||
|
||||
public boolean saveVersionSetting(String id) {
|
||||
if (!versionSettings.containsKey(id))
|
||||
return false;
|
||||
File file = getVersionSettingFile(id);
|
||||
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
|
||||
return false;
|
||||
|
||||
try {
|
||||
FileUtils.writeText(file, GSON.toJson(versionSettings.get(id)));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to save version setting of " + id, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make version use self version settings instead of the global one.
|
||||
* @param id the version id.
|
||||
* @return specialized version setting, null if given version does not exist.
|
||||
*/
|
||||
public VersionSetting specializeVersionSetting(String id) {
|
||||
VersionSetting vs = getVersionSetting(id);
|
||||
if (vs == null)
|
||||
vs = createVersionSetting(id);
|
||||
if (vs == null)
|
||||
return null;
|
||||
vs.setUsesGlobal(false);
|
||||
return vs;
|
||||
}
|
||||
|
||||
public void globalizeVersionSetting(String id) {
|
||||
VersionSetting vs = getVersionSetting(id);
|
||||
if (vs != null)
|
||||
vs.setUsesGlobal(true);
|
||||
}
|
||||
|
||||
public boolean forbidsVersion(String id) {
|
||||
return FORBIDDEN.contains(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getModpackConfiguration(String version) {
|
||||
return new File(getVersionRoot(version), "modpack.cfg");
|
||||
}
|
||||
|
||||
public void markVersionAsModpack(String id) {
|
||||
beingModpackVersions.add(id);
|
||||
}
|
||||
|
||||
public void undoMark(String id) {
|
||||
beingModpackVersions.remove(id);
|
||||
}
|
||||
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
private static final HashSet<String> FORBIDDEN = new HashSet<>(Arrays.asList("modpack", "minecraftinstance", "manifest"));
|
||||
|
||||
private static final String PROFILE = "{\"selectedProfile\": \"(Default)\",\"profiles\": {\"(Default)\": {\"name\": \"(Default)\"}},\"clientToken\": \"88888888-8888-8888-8888-888888888888\"}";
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.Zipper;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Export the game to a mod pack file.
|
||||
*/
|
||||
public class HMCLModpackExportTask extends Task<Void> {
|
||||
private final DefaultGameRepository repository;
|
||||
private final String version;
|
||||
private final List<String> whitelist;
|
||||
private final Modpack modpack;
|
||||
private final File output;
|
||||
|
||||
/**
|
||||
* @param output mod pack file.
|
||||
* @param version to locate version.json
|
||||
*/
|
||||
public HMCLModpackExportTask(DefaultGameRepository repository, String version, List<String> whitelist, Modpack modpack, File output) {
|
||||
this.repository = repository;
|
||||
this.version = version;
|
||||
this.whitelist = whitelist;
|
||||
this.modpack = modpack;
|
||||
this.output = output;
|
||||
|
||||
onDone().register(event -> {
|
||||
if (event.isFailed()) output.delete();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
ArrayList<String> blackList = new ArrayList<>(HMCLModpackManager.MODPACK_BLACK_LIST);
|
||||
blackList.add(version + ".jar");
|
||||
blackList.add(version + ".json");
|
||||
Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
|
||||
try (Zipper zip = new Zipper(output.toPath())) {
|
||||
zip.putDirectory(repository.getRunDirectory(version).toPath(), "minecraft", path -> {
|
||||
if (path.isEmpty())
|
||||
return true;
|
||||
for (String s : blackList)
|
||||
if (path.equals(s))
|
||||
return false;
|
||||
for (String s : whitelist)
|
||||
if (path.equals(s))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
Version mv = repository.getResolvedVersion(version);
|
||||
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
|
||||
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
|
||||
zip.putTextFile(JsonUtils.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.download.DependencyManager;
|
||||
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
|
||||
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.mod.ModpackInstallTask;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public final class HMCLModpackInstallTask extends Task<Void> {
|
||||
private final File zipFile;
|
||||
private final String name;
|
||||
private final HMCLGameRepository repository;
|
||||
private final Modpack modpack;
|
||||
private final List<Task<?>> dependencies = new LinkedList<>();
|
||||
private final List<Task<?>> dependents = new LinkedList<>();
|
||||
|
||||
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) {
|
||||
DependencyManager dependency = profile.getDependency();
|
||||
repository = profile.getRepository();
|
||||
this.zipFile = zipFile;
|
||||
this.name = name;
|
||||
this.modpack = modpack;
|
||||
|
||||
File run = repository.getRunDirectory(name);
|
||||
File json = repository.getModpackConfiguration(name);
|
||||
if (repository.hasVersion(name) && !json.exists())
|
||||
throw new IllegalArgumentException("Version " + name + " already exists");
|
||||
|
||||
dependents.add(dependency.gameBuilder().name(name).gameVersion(modpack.getGameVersion()).buildAsync());
|
||||
|
||||
onDone().register(event -> {
|
||||
if (event.isFailed()) repository.removeVersionFromDisk(name);
|
||||
});
|
||||
|
||||
ModpackConfiguration<Modpack> config = null;
|
||||
try {
|
||||
if (json.exists()) {
|
||||
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
|
||||
}.getType());
|
||||
|
||||
if (!MODPACK_TYPE.equals(config.getType()))
|
||||
throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version.");
|
||||
}
|
||||
} catch (JsonParseException | IOException ignore) {
|
||||
}
|
||||
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/minecraft", it -> !"pack.json".equals(it), config));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task<?>> getDependencies() {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Task<?>> getDependents() {
|
||||
return dependents;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
|
||||
Version version = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
|
||||
dependencies.add(new VersionJsonSaveTask(repository, version));
|
||||
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
|
||||
}
|
||||
|
||||
public static final String MODPACK_TYPE = "HMCL";
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.mod.Modpack;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class HMCLModpackManager {
|
||||
|
||||
public static final List<String> MODPACK_BLACK_LIST = Lang.immutableListOf(
|
||||
"usernamecache.json", "usercache.json", // Minecraft
|
||||
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
|
||||
"pack.json", "launcher.jar", "hmclmc.log", "cache", // HMCL
|
||||
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
|
||||
"minetweaker.log", // Mods
|
||||
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
|
||||
"downloads", // Curse
|
||||
"asm", "backups", "TCNodeTracker", "CustomDISkins", "data" // Mods
|
||||
);
|
||||
public static final List<String> MODPACK_SUGGESTED_BLACK_LIST = Lang.immutableListOf(
|
||||
"fonts", // BetterFonts
|
||||
"saves", "servers.dat", "options.txt", // Minecraft
|
||||
"blueprints" /* BuildCraft */,
|
||||
"optionsof.txt" /* OptiFine */,
|
||||
"journeymap" /* JourneyMap */,
|
||||
"optionsshaders.txt",
|
||||
"mods/VoxelMods");
|
||||
|
||||
public static ModAdviser.ModSuggestion suggestMod(String fileName, boolean isDirectory) {
|
||||
if (match(MODPACK_BLACK_LIST, fileName, isDirectory))
|
||||
return ModAdviser.ModSuggestion.HIDDEN;
|
||||
if (match(MODPACK_SUGGESTED_BLACK_LIST, fileName, isDirectory))
|
||||
return ModAdviser.ModSuggestion.NORMAL;
|
||||
else
|
||||
return ModAdviser.ModSuggestion.SUGGESTED;
|
||||
}
|
||||
|
||||
private static boolean match(List<String> l, String fileName, boolean isDirectory) {
|
||||
for (String s : l)
|
||||
if (isDirectory) {
|
||||
if (fileName.startsWith(s + "/"))
|
||||
return true;
|
||||
} else if (fileName.equals(s))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the manifest in a HMCL modpack.
|
||||
*
|
||||
* @param file a HMCL modpack file.
|
||||
* @param encoding encoding of modpack zip file.
|
||||
* @throws IOException if the file is not a valid zip file.
|
||||
* @throws JsonParseException if the manifest.json is missing or malformed.
|
||||
* @return the manifest of HMCL modpack.
|
||||
*/
|
||||
public static Modpack readHMCLModpackManifest(Path file, Charset encoding) throws IOException, JsonParseException {
|
||||
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json", encoding);
|
||||
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, Modpack.class).setEncoding(encoding);
|
||||
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json", encoding);
|
||||
Version game = JsonUtils.fromNonNullJson(gameJson, Version.class);
|
||||
if (game.getJar() == null)
|
||||
if (StringUtils.isBlank(manifest.getVersion()))
|
||||
throw new JsonParseException("Cannot recognize the game version of modpack " + file + ".");
|
||||
else
|
||||
return manifest.setManifest(HMCLModpackManifest.INSTANCE);
|
||||
else
|
||||
return manifest.setManifest(HMCLModpackManifest.INSTANCE).setGameVersion(game.getJar());
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
public final class HMCLModpackManifest {
|
||||
public static final HMCLModpackManifest INSTANCE = new HMCLModpackManifest();
|
||||
|
||||
private HMCLModpackManifest() {}
|
||||
}
|
||||
@@ -1,579 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
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.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.download.MaintainTask;
|
||||
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
|
||||
import org.jackhuang.hmcl.launch.*;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionException;
|
||||
import org.jackhuang.hmcl.mod.CurseCompletionTask;
|
||||
import org.jackhuang.hmcl.mod.ModpackConfiguration;
|
||||
import org.jackhuang.hmcl.setting.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
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.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.Pair;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
|
||||
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.platform.CommandBuilder;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.ManagedProcess;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class LauncherHelper {
|
||||
|
||||
private final Profile profile;
|
||||
private final Account account;
|
||||
private final String selectedVersion;
|
||||
private File scriptFile;
|
||||
private final VersionSetting setting;
|
||||
private LauncherVisibility launcherVisibility;
|
||||
private boolean showLogs;
|
||||
|
||||
public LauncherHelper(Profile profile, Account account, String selectedVersion) {
|
||||
this.profile = Objects.requireNonNull(profile);
|
||||
this.account = Objects.requireNonNull(account);
|
||||
this.selectedVersion = Objects.requireNonNull(selectedVersion);
|
||||
this.setting = profile.getVersionSetting(selectedVersion);
|
||||
this.launcherVisibility = setting.getLauncherVisibility();
|
||||
this.showLogs = setting.isShowLogs();
|
||||
}
|
||||
|
||||
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
|
||||
|
||||
public void setTestMode() {
|
||||
launcherVisibility = LauncherVisibility.KEEP;
|
||||
showLogs = true;
|
||||
}
|
||||
|
||||
public void launch() {
|
||||
Logging.LOG.info("Launching game version: " + selectedVersion);
|
||||
|
||||
GameRepository repository = profile.getRepository();
|
||||
Version version = repository.getResolvedVersion(selectedVersion);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
checkGameState(profile, setting, version, () -> {
|
||||
Controllers.dialog(launchingStepsPane);
|
||||
Schedulers.newThread().execute(this::launch0);
|
||||
});
|
||||
} catch (InterruptedException ignore) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void makeLaunchScript(File scriptFile) {
|
||||
this.scriptFile = Objects.requireNonNull(scriptFile);
|
||||
|
||||
launch();
|
||||
}
|
||||
|
||||
private void launch0() {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
DefaultDependencyManager dependencyManager = profile.getDependency();
|
||||
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
|
||||
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
|
||||
|
||||
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
|
||||
.thenCompose(() -> {
|
||||
if (setting.isNotCheckGame())
|
||||
return null;
|
||||
else
|
||||
return dependencyManager.checkGameCompletionAsync(version);
|
||||
})
|
||||
.thenRun(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))
|
||||
.thenCompose(() -> {
|
||||
try {
|
||||
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
|
||||
if ("Curse".equals(configuration.getType()))
|
||||
return new CurseCompletionTask(dependencyManager, selectedVersion);
|
||||
else
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.thenRun(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))
|
||||
.thenSupply(i18n("account.methods"), () -> {
|
||||
try {
|
||||
return account.logIn();
|
||||
} catch (CredentialExpiredException e) {
|
||||
LOG.info("Credential has expired: " + e);
|
||||
return DialogController.logIn(account);
|
||||
} catch (AuthenticationException e) {
|
||||
LOG.warning("Authentication failed, try playing offline: " + e);
|
||||
return account.playOffline().orElseThrow(() -> e);
|
||||
}
|
||||
})
|
||||
.thenApply(Schedulers.javafx(), authInfo -> {
|
||||
emitStatus(LoadingState.LAUNCHING);
|
||||
return authInfo;
|
||||
})
|
||||
.thenApply(authInfo -> new HMCLGameLauncher(
|
||||
repository,
|
||||
selectedVersion,
|
||||
authInfo,
|
||||
setting.toLaunchOptions(profile.getGameDir()),
|
||||
launcherVisibility == LauncherVisibility.CLOSE
|
||||
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
|
||||
: new HMCLProcessListener(authInfo, gameVersion.isPresent())
|
||||
))
|
||||
.thenCompose(launcher -> { // launcher is prev task's result
|
||||
if (scriptFile == null) {
|
||||
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
|
||||
} else {
|
||||
return new LaunchTask<ManagedProcess>(() -> {
|
||||
launcher.makeLaunchScript(scriptFile);
|
||||
return null;
|
||||
}).setName(i18n("version.launch_script"));
|
||||
}
|
||||
})
|
||||
.thenAccept(process -> { // process is LaunchTask's result
|
||||
if (scriptFile == null) {
|
||||
PROCESSES.add(process);
|
||||
if (launcherVisibility == LauncherVisibility.CLOSE)
|
||||
Launcher.stopApplication();
|
||||
else
|
||||
launchingStepsPane.setCancel(it -> {
|
||||
process.stop();
|
||||
it.fireEvent(new DialogCloseEvent());
|
||||
});
|
||||
} else {
|
||||
Platform.runLater(() -> {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
|
||||
});
|
||||
}
|
||||
})
|
||||
.executor();
|
||||
|
||||
launchingStepsPane.setExecutor(executor, false);
|
||||
executor.addTaskListener(new TaskListener() {
|
||||
final AtomicInteger finished = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public void onFinished(Task<?> task) {
|
||||
finished.incrementAndGet();
|
||||
int runningTasks = executor.getRunningTasks();
|
||||
Platform.runLater(() -> launchingStepsPane.setProgress(1.0 * finished.get() / runningTasks));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop(boolean success, TaskExecutor executor) {
|
||||
if (!success && !Controllers.isStopped()) {
|
||||
Platform.runLater(() -> {
|
||||
// Check if the application has stopped
|
||||
// because onStop will be invoked if tasks fail when the executor service shut down.
|
||||
if (!Controllers.isStopped()) {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
Exception ex = executor.getException();
|
||||
if (ex != null) {
|
||||
String message;
|
||||
if (ex instanceof CurseCompletionException) {
|
||||
if (ex.getCause() instanceof FileNotFoundException)
|
||||
message = i18n("modpack.type.curse.not_found");
|
||||
else
|
||||
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 if (ex instanceof LibraryDownloadException) {
|
||||
message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(ex.getCause());
|
||||
} else {
|
||||
message = StringUtils.getStackTrace(ex);
|
||||
}
|
||||
Controllers.dialog(message,
|
||||
scriptFile == null ? i18n("launch.failed") : i18n("version.launch_script.failed"),
|
||||
MessageType.ERROR);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
launchingStepsPane.setExecutor(null);
|
||||
}
|
||||
});
|
||||
|
||||
executor.start();
|
||||
}
|
||||
|
||||
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
|
||||
if (setting.isNotCheckJVM()) {
|
||||
onAccept.run();
|
||||
return;
|
||||
}
|
||||
|
||||
boolean flag = false;
|
||||
boolean java8required = false;
|
||||
boolean newJavaRequired = false;
|
||||
|
||||
// Without onAccept called, the launching operation will be terminated.
|
||||
|
||||
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
|
||||
JavaVersion java = setting.getJavaVersion();
|
||||
if (java == null) {
|
||||
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
setting.setJava(null);
|
||||
setting.setDefaultJavaPath(null);
|
||||
java = JavaVersion.fromCurrentEnvironment();
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// Game later than 1.7.2 accepts Java 8.
|
||||
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8 && gameVersion.compareTo(VersionNumber.asVersion("1.7.2")) > 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_8)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
newJavaRequired = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
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(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, null);
|
||||
} else {
|
||||
// Most mods require Java 8 or later version.
|
||||
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
}
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// LaunchWrapper 1.12 will crash because of assuming the system class loader is an instance of URLClassLoader.
|
||||
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9
|
||||
&& version.getMainClass().contains("launchwrapper")
|
||||
&& version.getLibraries().stream()
|
||||
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
|
||||
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
|
||||
if (java8.isPresent()) {
|
||||
java8required = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageType.INFORMATION, onAccept);
|
||||
flag = true;
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, null);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
|
||||
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0_51");
|
||||
if (!flag && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && java.getParsedVersion() == JavaVersion.JAVA_8 && java.getVersionNumber().compareTo(JAVA_8) < 0) {
|
||||
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
if (java8.isPresent()) {
|
||||
newJavaRequired = true;
|
||||
setting.setJavaVersion(java8.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageType.WARNING, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
|
||||
org.jackhuang.hmcl.util.platform.Platform.IS_64_BIT) {
|
||||
final JavaVersion java32 = java;
|
||||
|
||||
// First find if same java version but whose platform is 64-bit installed.
|
||||
Optional<JavaVersion> java64 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
|
||||
.filter(javaVersion -> javaVersion.getParsedVersion() == java32.getParsedVersion())
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
|
||||
if (!java64.isPresent()) {
|
||||
final boolean java8requiredFinal = java8required, newJavaRequiredFinal = newJavaRequired;
|
||||
|
||||
// Then find if other java version which satisfies requirements installed.
|
||||
java64 = JavaVersion.getJavas().stream()
|
||||
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
|
||||
.filter(javaVersion -> {
|
||||
if (java8requiredFinal) return javaVersion.getParsedVersion() == JavaVersion.JAVA_8;
|
||||
if (newJavaRequiredFinal) return javaVersion.getParsedVersion() >= JavaVersion.JAVA_8;
|
||||
return true;
|
||||
})
|
||||
.max(Comparator.comparing(JavaVersion::getVersionNumber));
|
||||
}
|
||||
|
||||
if (java64.isPresent()) {
|
||||
setting.setJavaVersion(java64.get());
|
||||
} else {
|
||||
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 32-bit JVM cannot make use of too much memory.
|
||||
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.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(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// Cannot allocate too much memory exceeding free space.
|
||||
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
|
||||
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
|
||||
// Forge 2760~2773 will crash game with LiteLoader.
|
||||
if (!flag) {
|
||||
boolean hasForge2760 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
|
||||
.anyMatch(it ->
|
||||
VersionNumber.VERSION_COMPARATOR.compare("1.12.2-14.23.5.2760", it.getVersion()) <= 0 &&
|
||||
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2773") < 0);
|
||||
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
|
||||
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
|
||||
Controllers.dialog(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), MessageType.ERROR, onAccept);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flag)
|
||||
onAccept.run();
|
||||
}
|
||||
|
||||
public void emitStatus(LoadingState state) {
|
||||
if (state == LoadingState.DONE) {
|
||||
launchingStepsPane.fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
launchingStepsPane.setTitle(state.getLocalizedMessage());
|
||||
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
|
||||
}
|
||||
|
||||
private void checkExit() {
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(Controllers.getStage()::show);
|
||||
break;
|
||||
case KEEP:
|
||||
// No operations here
|
||||
break;
|
||||
case CLOSE:
|
||||
throw new Error("Never get to here");
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
// Shut down the platform when user closed log window.
|
||||
Platform.setImplicitExit(true);
|
||||
// If we use Launcher.stop(), log window will be halt immediately.
|
||||
Launcher.stopWithoutPlatform();
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static class LaunchTask<T> extends Task<T> {
|
||||
private final ExceptionalSupplier<T, Exception> supplier;
|
||||
|
||||
public LaunchTask(ExceptionalSupplier<T, Exception> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() throws Exception {
|
||||
setResult(supplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The managed process listener.
|
||||
* Guarantee that one [JavaProcess], one [HMCLProcessListener].
|
||||
* Because every time we launched a game, we generates a new [HMCLProcessListener]
|
||||
*/
|
||||
class HMCLProcessListener implements ProcessListener {
|
||||
|
||||
private final Map<String, String> forbiddenTokens;
|
||||
private ManagedProcess process;
|
||||
private boolean lwjgl;
|
||||
private LogWindow logWindow;
|
||||
private final boolean detectWindow;
|
||||
private final LinkedList<Pair<String, Log4jLevel>> logs;
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
public HMCLProcessListener(AuthInfo authInfo, boolean detectWindow) {
|
||||
this.detectWindow = detectWindow;
|
||||
|
||||
if (authInfo == null)
|
||||
forbiddenTokens = Collections.emptyMap();
|
||||
else
|
||||
forbiddenTokens = mapOf(
|
||||
pair(authInfo.getAccessToken(), "<access token>"),
|
||||
pair(UUIDTypeAdapter.fromUUID(authInfo.getUUID()), "<uuid>"),
|
||||
pair(authInfo.getUsername(), "<player>")
|
||||
);
|
||||
|
||||
logs = new LinkedList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProcess(ManagedProcess process) {
|
||||
this.process = process;
|
||||
|
||||
if (showLogs)
|
||||
Platform.runLater(() -> {
|
||||
logWindow = new LogWindow();
|
||||
logWindow.show();
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
|
||||
private void finishLaunch() {
|
||||
switch (launcherVisibility) {
|
||||
case HIDE_AND_REOPEN:
|
||||
Platform.runLater(() -> {
|
||||
// If application was stopped and execution services did not finish termination,
|
||||
// these codes will be executed.
|
||||
if (Controllers.getStage() != null) {
|
||||
Controllers.getStage().hide();
|
||||
emitStatus(LoadingState.DONE);
|
||||
}
|
||||
});
|
||||
break;
|
||||
case CLOSE:
|
||||
// Never come to here.
|
||||
break;
|
||||
case KEEP:
|
||||
Platform.runLater(() -> {
|
||||
emitStatus(LoadingState.DONE);
|
||||
});
|
||||
break;
|
||||
case HIDE:
|
||||
Platform.runLater(() -> {
|
||||
// If application was stopped and execution services did not finish termination,
|
||||
// these codes will be executed.
|
||||
if (Controllers.getStage() != null) {
|
||||
Controllers.getStage().close();
|
||||
emitStatus(LoadingState.DONE);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onLog(String log, Log4jLevel level) {
|
||||
String newLog = log;
|
||||
for (Map.Entry<String, String> entry : forbiddenTokens.entrySet())
|
||||
newLog = newLog.replace(entry.getKey(), entry.getValue());
|
||||
|
||||
if (level.lessOrEqual(Log4jLevel.ERROR))
|
||||
System.err.print(log);
|
||||
else
|
||||
System.out.print(log);
|
||||
|
||||
logs.add(pair(log, level));
|
||||
if (logs.size() > config().getLogLines())
|
||||
logs.removeFirst();
|
||||
|
||||
if (showLogs) {
|
||||
try {
|
||||
latch.await();
|
||||
logWindow.waitForLoaded();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> logWindow.logLine(log, level));
|
||||
}
|
||||
|
||||
if (!lwjgl && (log.contains("LWJGL Version: ") || !detectWindow)) {
|
||||
lwjgl = true;
|
||||
finishLaunch();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExit(int exitCode, ExitType exitType) {
|
||||
if (exitType == ExitType.INTERRUPTED)
|
||||
return;
|
||||
|
||||
// Game crashed before opening the game window.
|
||||
if (!lwjgl) finishLaunch();
|
||||
|
||||
if (exitType != ExitType.NORMAL && logWindow == null)
|
||||
Platform.runLater(() -> {
|
||||
logWindow = new LogWindow();
|
||||
|
||||
switch (exitType) {
|
||||
case JVM_ERROR:
|
||||
logWindow.setTitle(i18n("launch.failed.cannot_create_jvm"));
|
||||
break;
|
||||
case APPLICATION_ERROR:
|
||||
logWindow.setTitle(i18n("launch.failed.exited_abnormally"));
|
||||
break;
|
||||
}
|
||||
|
||||
logWindow.show();
|
||||
logWindow.onDone.register(() -> {
|
||||
logWindow.logLine("Command: " + new CommandBuilder().addAll(process.getCommands()).toString(), Log4jLevel.INFO);
|
||||
for (Map.Entry<String, Log4jLevel> entry : logs)
|
||||
logWindow.logLine(entry.getKey(), entry.getValue());
|
||||
});
|
||||
});
|
||||
|
||||
checkExit();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
|
||||
public static void stopManagedProcesses() {
|
||||
while (!PROCESSES.isEmpty())
|
||||
Optional.ofNullable(PROCESSES.poll()).ifPresent(ManagedProcess::stop);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public enum LoadingState {
|
||||
DEPENDENCIES("launch.state.dependencies"),
|
||||
MODS("launch.state.modpack"),
|
||||
LOGGING_IN("launch.state.logging_in"),
|
||||
LAUNCHING("launch.state.waiting_launching"),
|
||||
DONE("launch.state.done");
|
||||
|
||||
private final String key;
|
||||
|
||||
LoadingState(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String getLocalizedMessage() {
|
||||
return i18n(key);
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.jackhuang.hmcl.mod.*;
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
|
||||
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
|
||||
import org.jackhuang.hmcl.util.gson.JsonUtils;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class ModpackHelper {
|
||||
private ModpackHelper() {}
|
||||
|
||||
public static Modpack readModpackManifest(Path file, Charset charset) throws UnsupportedModpackException {
|
||||
try {
|
||||
return CurseManifest.readCurseForgeModpackManifest(file, charset);
|
||||
} catch (Exception e) {
|
||||
// ignore it, not a valid CurseForge modpack.
|
||||
}
|
||||
|
||||
try {
|
||||
return HMCLModpackManager.readHMCLModpackManifest(file, charset);
|
||||
} catch (Exception e) {
|
||||
// ignore it, not a valid HMCL modpack.
|
||||
}
|
||||
|
||||
try {
|
||||
return MultiMCInstanceConfiguration.readMultiMCModpackManifest(file, charset);
|
||||
} catch (Exception e) {
|
||||
// ignore it, not a valid MultiMC modpack.
|
||||
}
|
||||
|
||||
throw new UnsupportedModpackException(file.toString());
|
||||
}
|
||||
|
||||
public static ModpackConfiguration<?> readModpackConfiguration(File file) throws IOException {
|
||||
if (!file.exists())
|
||||
throw new FileNotFoundException(file.getPath());
|
||||
else
|
||||
try {
|
||||
return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
|
||||
}.getType());
|
||||
} catch (JsonParseException e) {
|
||||
throw new IOException("Malformed modpack configuration");
|
||||
}
|
||||
}
|
||||
|
||||
private static String getManifestType(Object manifest) throws UnsupportedModpackException {
|
||||
if (manifest instanceof HMCLModpackManifest)
|
||||
return HMCLModpackInstallTask.MODPACK_TYPE;
|
||||
else if (manifest instanceof MultiMCInstanceConfiguration)
|
||||
return MultiMCModpackInstallTask.MODPACK_TYPE;
|
||||
else if (manifest instanceof CurseManifest)
|
||||
return CurseInstallTask.MODPACK_TYPE;
|
||||
else
|
||||
throw new UnsupportedModpackException();
|
||||
}
|
||||
|
||||
public static Task<Void> getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) {
|
||||
profile.getRepository().markVersionAsModpack(name);
|
||||
|
||||
ExceptionalRunnable<?> success = () -> {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
repository.refreshVersions();
|
||||
VersionSetting vs = repository.specializeVersionSetting(name);
|
||||
repository.undoMark(name);
|
||||
if (vs != null)
|
||||
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||
};
|
||||
|
||||
ExceptionalConsumer<Exception, ?> failure = ex -> {
|
||||
if (ex instanceof CurseCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
|
||||
success.run();
|
||||
// This is tolerable and we will not delete the game
|
||||
} else {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
repository.removeVersionFromDisk(name);
|
||||
}
|
||||
};
|
||||
|
||||
if (modpack.getManifest() instanceof CurseManifest)
|
||||
return new CurseInstallTask(profile.getDependency(), zipFile, modpack, ((CurseManifest) modpack.getManifest()), name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure);
|
||||
else if (modpack.getManifest() instanceof HMCLModpackManifest)
|
||||
return new HMCLModpackInstallTask(profile, zipFile, modpack, name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure);
|
||||
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
|
||||
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
|
||||
.whenComplete(Schedulers.defaultScheduler(), success, failure)
|
||||
.thenCompose(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
|
||||
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
|
||||
}
|
||||
|
||||
public static Task<Void> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException {
|
||||
Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset);
|
||||
|
||||
switch (configuration.getType()) {
|
||||
case CurseInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof CurseManifest))
|
||||
throw new MismatchedModpackTypeException(CurseInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new CurseInstallTask(profile.getDependency(), zipFile, modpack, (CurseManifest) modpack.getManifest(), name));
|
||||
case MultiMCModpackInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof MultiMCInstanceConfiguration))
|
||||
throw new MismatchedModpackTypeException(MultiMCModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, (MultiMCInstanceConfiguration) modpack.getManifest(), name));
|
||||
case HMCLModpackInstallTask.MODPACK_TYPE:
|
||||
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
|
||||
throw new MismatchedModpackTypeException(HMCLModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
|
||||
|
||||
return new ModpackUpdateTask(profile.getRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
|
||||
default:
|
||||
throw new UnsupportedModpackException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void toVersionSetting(MultiMCInstanceConfiguration c, VersionSetting vs) {
|
||||
vs.setUsesGlobal(false);
|
||||
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
|
||||
|
||||
if (c.isOverrideJavaLocation()) {
|
||||
vs.setJavaDir(Lang.nonNull(c.getJavaPath(), ""));
|
||||
}
|
||||
|
||||
if (c.isOverrideMemory()) {
|
||||
vs.setPermSize(Optional.ofNullable(c.getPermGen()).map(Object::toString).orElse(""));
|
||||
if (c.getMaxMemory() != null)
|
||||
vs.setMaxMemory(c.getMaxMemory());
|
||||
vs.setMinMemory(c.getMinMemory());
|
||||
}
|
||||
|
||||
if (c.isOverrideCommands()) {
|
||||
vs.setWrapper(Lang.nonNull(c.getWrapperCommand(), ""));
|
||||
vs.setPreLaunchCommand(Lang.nonNull(c.getPreLaunchCommand(), ""));
|
||||
}
|
||||
|
||||
if (c.isOverrideJavaArgs()) {
|
||||
vs.setJavaArgs(Lang.nonNull(c.getJvmArgs(), ""));
|
||||
}
|
||||
|
||||
if (c.isOverrideConsole()) {
|
||||
vs.setShowLogs(c.isShowConsole());
|
||||
}
|
||||
|
||||
if (c.isOverrideWindow()) {
|
||||
vs.setFullscreen(c.isFullscreen());
|
||||
if (c.getWidth() != null)
|
||||
vs.setWidth(c.getWidth());
|
||||
if (c.getHeight() != null)
|
||||
vs.setHeight(c.getHeight());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import org.jackhuang.hmcl.mod.MultiMCInstanceConfiguration;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.VersionSetting;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public final class MultiMCInstallVersionSettingTask extends Task<Void> {
|
||||
private final Profile profile;
|
||||
private final MultiMCInstanceConfiguration manifest;
|
||||
private final String version;
|
||||
|
||||
public MultiMCInstallVersionSettingTask(Profile profile, MultiMCInstanceConfiguration manifest, String version) {
|
||||
this.profile = profile;
|
||||
this.manifest = manifest;
|
||||
this.version = version;
|
||||
|
||||
setExecutor(Schedulers.javafx());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
VersionSetting vs = Objects.requireNonNull(profile.getRepository().specializeVersionSetting(version));
|
||||
ModpackHelper.toVersionSetting(manifest, vs);
|
||||
}
|
||||
}
|
||||
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.game;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.util.Lang.threadPool;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.task.FileDownloadTask;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
/**
|
||||
* @author yushijinhun
|
||||
*/
|
||||
public final class TexturesLoader {
|
||||
|
||||
private TexturesLoader() {
|
||||
}
|
||||
|
||||
// ==== Texture Loading ====
|
||||
public static class LoadedTexture {
|
||||
private final BufferedImage image;
|
||||
private final Map<String, String> metadata;
|
||||
|
||||
public LoadedTexture(BufferedImage image, Map<String, String> metadata) {
|
||||
this.image = requireNonNull(image);
|
||||
this.metadata = requireNonNull(metadata);
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public Map<String, String> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS);
|
||||
private static final Path TEXTURES_DIR = Metadata.MINECRAFT_DIRECTORY.resolve("assets").resolve("skins");
|
||||
|
||||
private static Path getTexturePath(Texture texture) {
|
||||
String url = texture.getUrl();
|
||||
int slash = url.lastIndexOf('/');
|
||||
int dot = url.lastIndexOf('.');
|
||||
if (dot < slash) {
|
||||
dot = url.length();
|
||||
}
|
||||
String hash = url.substring(slash + 1, dot);
|
||||
String prefix = hash.length() > 2 ? hash.substring(0, 2) : "xx";
|
||||
return TEXTURES_DIR.resolve(prefix).resolve(hash);
|
||||
}
|
||||
|
||||
public static LoadedTexture loadTexture(Texture texture) throws IOException {
|
||||
if (StringUtils.isBlank(texture.getUrl())) {
|
||||
throw new IOException("Texture url is empty");
|
||||
}
|
||||
|
||||
Path file = getTexturePath(texture);
|
||||
if (!Files.isRegularFile(file)) {
|
||||
// download it
|
||||
try {
|
||||
new FileDownloadTask(new URL(texture.getUrl()), file.toFile()).run();
|
||||
LOG.info("Texture downloaded: " + texture.getUrl());
|
||||
} catch (Exception e) {
|
||||
if (Files.isRegularFile(file)) {
|
||||
// concurrency conflict?
|
||||
LOG.log(Level.WARNING, "Failed to download texture " + texture.getUrl() + ", but the file is available", e);
|
||||
} else {
|
||||
throw new IOException("Failed to download texture " + texture.getUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BufferedImage img;
|
||||
try (InputStream in = Files.newInputStream(file)) {
|
||||
img = ImageIO.read(in);
|
||||
}
|
||||
Map<String, String> metadata = texture.getMetadata();
|
||||
if (metadata == null) {
|
||||
metadata = emptyMap();
|
||||
}
|
||||
return new LoadedTexture(img, metadata);
|
||||
}
|
||||
// ====
|
||||
|
||||
// ==== Skins ====
|
||||
private final static Map<TextureModel, LoadedTexture> DEFAULT_SKINS = new EnumMap<>(TextureModel.class);
|
||||
static {
|
||||
loadDefaultSkin("/assets/img/steve.png", TextureModel.STEVE);
|
||||
loadDefaultSkin("/assets/img/alex.png", TextureModel.ALEX);
|
||||
}
|
||||
private static void loadDefaultSkin(String path, TextureModel model) {
|
||||
try (InputStream in = TexturesLoader.class.getResourceAsStream(path)) {
|
||||
DEFAULT_SKINS.put(model, new LoadedTexture(ImageIO.read(in), singletonMap("model", model.modelName)));
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static LoadedTexture getDefaultSkin(TextureModel model) {
|
||||
return DEFAULT_SKINS.get(model);
|
||||
}
|
||||
|
||||
public static ObjectBinding<LoadedTexture> skinBinding(YggdrasilService service, UUID uuid) {
|
||||
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(uuid));
|
||||
return BindingMapping.of(service.getProfileRepository().binding(uuid))
|
||||
.map(profile -> profile
|
||||
.flatMap(it -> {
|
||||
try {
|
||||
return YggdrasilService.getTextures(it);
|
||||
} catch (ServerResponseMalformedException e) {
|
||||
LOG.log(Level.WARNING, "Failed to parse texture payload", e);
|
||||
return Optional.empty();
|
||||
}
|
||||
})
|
||||
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))
|
||||
.filter(it -> StringUtils.isNotBlank(it.getUrl())))
|
||||
.asyncMap(it -> {
|
||||
if (it.isPresent()) {
|
||||
Texture texture = it.get();
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
return loadTexture(texture);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
|
||||
return uuidFallback;
|
||||
}
|
||||
}, POOL);
|
||||
} else {
|
||||
return CompletableFuture.completedFuture(uuidFallback);
|
||||
}
|
||||
}, uuidFallback);
|
||||
}
|
||||
// ====
|
||||
|
||||
// ==== Avatar ====
|
||||
public static BufferedImage toAvatar(BufferedImage skin, int size) {
|
||||
BufferedImage avatar = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
|
||||
Graphics2D g = avatar.createGraphics();
|
||||
|
||||
int scale = skin.getWidth() / 64;
|
||||
int faceOffset = (int) Math.round(size / 18.0);
|
||||
g.drawImage(skin,
|
||||
faceOffset, faceOffset, size - faceOffset, size - faceOffset,
|
||||
8 * scale, 8 * scale, 16 * scale, 16 * scale,
|
||||
null);
|
||||
g.drawImage(skin,
|
||||
0, 0, size, size,
|
||||
40 * scale, 8 * scale, 48 * scale, 16 * scale, null);
|
||||
|
||||
g.dispose();
|
||||
return avatar;
|
||||
}
|
||||
|
||||
public static ObjectBinding<Image> fxAvatarBinding(YggdrasilService service, UUID uuid, int size) {
|
||||
return BindingMapping.of(skinBinding(service, uuid))
|
||||
.map(it -> toAvatar(it.image, size))
|
||||
.map(img -> SwingFXUtils.toFXImage(img, null));
|
||||
}
|
||||
|
||||
public static ObjectBinding<Image> fxAvatarBinding(Account account, int size) {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
return fxAvatarBinding(((YggdrasilAccount) account).getYggdrasilService(), account.getUUID(), size);
|
||||
} else {
|
||||
return Bindings.createObjectBinding(
|
||||
() -> SwingFXUtils.toFXImage(toAvatar(getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image, size), null));
|
||||
}
|
||||
}
|
||||
// ====
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyListProperty;
|
||||
import javafx.beans.property.ReadOnlyListWrapper;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AccountFactory;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactProvider;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloader;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.SimpleAuthlibInjectorArtifactProvider;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Accounts {
|
||||
private Accounts() {}
|
||||
|
||||
public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE;
|
||||
public static final YggdrasilAccountFactory FACTORY_MOJANG = YggdrasilAccountFactory.MOJANG;
|
||||
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(createAuthlibInjectorArtifactProvider(), Accounts::getOrCreateAuthlibInjectorServer);
|
||||
|
||||
// ==== login type / account factory mapping ====
|
||||
private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>();
|
||||
private static final Map<AccountFactory<?>, String> factory2type = new HashMap<>();
|
||||
static {
|
||||
type2factory.put("offline", FACTORY_OFFLINE);
|
||||
type2factory.put("yggdrasil", FACTORY_MOJANG);
|
||||
type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR);
|
||||
|
||||
type2factory.forEach((type, factory) -> factory2type.put(factory, type));
|
||||
}
|
||||
|
||||
public static String getLoginType(AccountFactory<?> factory) {
|
||||
return Optional.ofNullable(factory2type.get(factory))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unrecognized account factory"));
|
||||
}
|
||||
|
||||
public static AccountFactory<?> getAccountFactory(String loginType) {
|
||||
return Optional.ofNullable(type2factory.get(loginType))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unrecognized login type"));
|
||||
}
|
||||
// ====
|
||||
|
||||
public static AccountFactory<?> getAccountFactory(Account account) {
|
||||
if (account instanceof OfflineAccount)
|
||||
return FACTORY_OFFLINE;
|
||||
else if (account instanceof AuthlibInjectorAccount)
|
||||
return FACTORY_AUTHLIB_INJECTOR;
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return FACTORY_MOJANG;
|
||||
else
|
||||
throw new IllegalArgumentException("Failed to determine account type: " + account);
|
||||
}
|
||||
|
||||
private static ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
|
||||
private static ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(Accounts.class, "accounts", accounts);
|
||||
|
||||
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>(Accounts.class, "selectedAccount") {
|
||||
{
|
||||
accounts.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
// this methods first checks whether the current selection is valid
|
||||
// if it's valid, the underlying storage will be updated
|
||||
// otherwise, the first account will be selected as an alternative(or null if accounts is empty)
|
||||
Account selected = get();
|
||||
if (accounts.isEmpty()) {
|
||||
if (selected == null) {
|
||||
// valid
|
||||
} else {
|
||||
// the previously selected account is gone, we can only set it to null here
|
||||
set(null);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (accounts.contains(selected)) {
|
||||
// valid
|
||||
} else {
|
||||
// the previously selected account is gone
|
||||
set(accounts.get(0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// selection is valid, store it
|
||||
if (!initialized)
|
||||
return;
|
||||
updateAccountStorages();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* True if {@link #init()} hasn't been called.
|
||||
*/
|
||||
private static boolean initialized = false;
|
||||
|
||||
static {
|
||||
accounts.addListener(onInvalidating(Accounts::updateAccountStorages));
|
||||
}
|
||||
|
||||
private static Map<Object, Object> getAccountStorage(Account account) {
|
||||
Map<Object, Object> storage = account.toStorage();
|
||||
storage.put("type", getLoginType(getAccountFactory(account)));
|
||||
if (account == selectedAccount.get()) {
|
||||
storage.put("selected", true);
|
||||
}
|
||||
return storage;
|
||||
}
|
||||
|
||||
private static void updateAccountStorages() {
|
||||
// don't update the underlying storage before data loading is completed
|
||||
// otherwise it might cause data loss
|
||||
if (!initialized)
|
||||
return;
|
||||
// update storage
|
||||
config().getAccountStorages().setAll(accounts.stream().map(Accounts::getAccountStorage).collect(toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it's ready to load accounts from {@link ConfigHolder#config()}.
|
||||
*/
|
||||
static void init() {
|
||||
if (initialized)
|
||||
throw new IllegalStateException("Already initialized");
|
||||
|
||||
// load accounts
|
||||
config().getAccountStorages().forEach(storage -> {
|
||||
AccountFactory<?> factory = type2factory.get(storage.get("type"));
|
||||
if (factory == null) {
|
||||
LOG.warning("Unrecognized account type: " + storage);
|
||||
return;
|
||||
}
|
||||
Account account;
|
||||
try {
|
||||
account = factory.fromStorage(storage);
|
||||
} catch (Exception e) {
|
||||
LOG.log(Level.WARNING, "Failed to load account: " + storage, e);
|
||||
return;
|
||||
}
|
||||
accounts.add(account);
|
||||
|
||||
if (Boolean.TRUE.equals(storage.get("selected"))) {
|
||||
selectedAccount.set(account);
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
|
||||
config().getAuthlibInjectorServers().addListener(onInvalidating(Accounts::removeDanglingAuthlibInjectorAccounts));
|
||||
|
||||
Account selected = selectedAccount.get();
|
||||
if (selected != null) {
|
||||
Schedulers.io().execute(() -> {
|
||||
try {
|
||||
selected.logIn();
|
||||
} catch (AuthenticationException e) {
|
||||
LOG.log(Level.WARNING, "Failed to log " + selected + " in", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (AuthlibInjectorServer server : config().getAuthlibInjectorServers()) {
|
||||
if (selected instanceof AuthlibInjectorAccount && ((AuthlibInjectorAccount) selected).getServer() == server)
|
||||
continue;
|
||||
Schedulers.io().execute(() -> {
|
||||
try {
|
||||
server.fetchMetadataResponse();
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to fetch authlib-injector server metdata: " + server, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static ObservableList<Account> getAccounts() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
public static ReadOnlyListProperty<Account> accountsProperty() {
|
||||
return accountsWrapper.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static Account getSelectedAccount() {
|
||||
return selectedAccount.get();
|
||||
}
|
||||
|
||||
public static void setSelectedAccount(Account selectedAccount) {
|
||||
Accounts.selectedAccount.set(selectedAccount);
|
||||
}
|
||||
|
||||
public static ObjectProperty<Account> selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
// ==== authlib-injector ====
|
||||
private static AuthlibInjectorArtifactProvider createAuthlibInjectorArtifactProvider() {
|
||||
String authlibinjectorLocation = System.getProperty("hmcl.authlibinjector.location");
|
||||
if (authlibinjectorLocation == null) {
|
||||
return new AuthlibInjectorDownloader(Metadata.HMCL_DIRECTORY, DownloadProviders::getDownloadProvider);
|
||||
} else {
|
||||
LOG.info("Using specified authlib-injector: " + authlibinjectorLocation);
|
||||
return new SimpleAuthlibInjectorArtifactProvider(Paths.get(authlibinjectorLocation));
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
|
||||
return config().getAuthlibInjectorServers().stream()
|
||||
.filter(server -> url.equals(server.getUrl()))
|
||||
.findFirst()
|
||||
.orElseGet(() -> {
|
||||
AuthlibInjectorServer server = new AuthlibInjectorServer(url);
|
||||
config().getAuthlibInjectorServers().add(server);
|
||||
return server;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
|
||||
* This method performs a check and removes the dangling accounts.
|
||||
*/
|
||||
private static void removeDanglingAuthlibInjectorAccounts() {
|
||||
accounts.stream()
|
||||
.filter(AuthlibInjectorAccount.class::isInstance)
|
||||
.map(AuthlibInjectorAccount.class::cast)
|
||||
.filter(it -> !config().getAuthlibInjectorServers().contains(it.getServer()))
|
||||
.collect(toList())
|
||||
.forEach(accounts::remove);
|
||||
}
|
||||
// ====
|
||||
|
||||
// ==== Login type name i18n ===
|
||||
private static Map<AccountFactory<?>, String> unlocalizedLoginTypeNames = mapOf(
|
||||
pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"),
|
||||
pair(Accounts.FACTORY_MOJANG, "account.methods.yggdrasil"),
|
||||
pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector"));
|
||||
|
||||
public static String getLocalizedLoginTypeName(AccountFactory<?> factory) {
|
||||
return i18n(Optional.ofNullable(unlocalizedLoginTypeNames.get(factory))
|
||||
.orElseThrow(() -> new IllegalArgumentException("Unrecognized account factory")));
|
||||
}
|
||||
// ====
|
||||
}
|
||||
Regular → Executable
+271
-376
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -13,451 +13,346 @@
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.core.download.DownloadType;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.collections.ObservableMap;
|
||||
import javafx.collections.ObservableSet;
|
||||
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.Metadata;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.util.gson.EnumOrdinalDeserializer;
|
||||
import org.jackhuang.hmcl.util.gson.FileTypeAdapter;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.awt.Font;
|
||||
import java.net.Proxy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.UUID;
|
||||
import org.jackhuang.hmcl.core.auth.AbstractAuthenticator;
|
||||
import org.jackhuang.hmcl.laf.LAFTheme;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.api.event.config.AuthenticatorChangedEvent;
|
||||
import org.jackhuang.hmcl.api.event.config.DownloadTypeChangedEvent;
|
||||
import org.jackhuang.hmcl.api.event.config.ThemeChangedEvent;
|
||||
import org.jackhuang.hmcl.core.MCUtils;
|
||||
import org.jackhuang.hmcl.util.sys.JdkVersion;
|
||||
import org.jackhuang.hmcl.util.sys.OS;
|
||||
import org.jackhuang.hmcl.api.auth.IAuthenticator;
|
||||
import org.jackhuang.hmcl.api.ui.Theme;
|
||||
|
||||
public final class Config implements Cloneable, Observable {
|
||||
|
||||
public static final int CURRENT_UI_VERSION = 0;
|
||||
|
||||
private static final Gson CONFIG_GSON = new GsonBuilder()
|
||||
.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))
|
||||
.registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType
|
||||
.registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy
|
||||
.setPrettyPrinting()
|
||||
.create();
|
||||
|
||||
@Nullable
|
||||
public static Config fromJson(String json) throws JsonParseException {
|
||||
Config loaded = CONFIG_GSON.fromJson(json, Config.class);
|
||||
if (loaded == null) {
|
||||
return null;
|
||||
}
|
||||
Config instance = new Config();
|
||||
PropertyUtils.copyProperties(loaded, instance);
|
||||
return instance;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Config implements Cloneable {
|
||||
|
||||
@SerializedName("last")
|
||||
private StringProperty selectedProfile = new SimpleStringProperty("");
|
||||
|
||||
@SerializedName("backgroundType")
|
||||
private ObjectProperty<EnumBackgroundImage> backgroundImageType = new SimpleObjectProperty<>(EnumBackgroundImage.DEFAULT);
|
||||
|
||||
private String last;
|
||||
@SerializedName("bgpath")
|
||||
private StringProperty backgroundImage = new SimpleStringProperty();
|
||||
|
||||
@SerializedName("commonDirType")
|
||||
private ObjectProperty<EnumCommonDirectory> commonDirType = new SimpleObjectProperty<>(EnumCommonDirectory.DEFAULT);
|
||||
|
||||
private String bgpath;
|
||||
@SerializedName("commonpath")
|
||||
private StringProperty commonDirectory = new SimpleStringProperty(Metadata.MINECRAFT_DIRECTORY.toString());
|
||||
|
||||
@SerializedName("hasProxy")
|
||||
private BooleanProperty hasProxy = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("hasProxyAuth")
|
||||
private BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
|
||||
|
||||
@SerializedName("proxyType")
|
||||
private ObjectProperty<Proxy.Type> proxyType = new SimpleObjectProperty<>(Proxy.Type.HTTP);
|
||||
|
||||
private String commonpath;
|
||||
@SerializedName("clientToken")
|
||||
private final String clientToken;
|
||||
@SerializedName("proxyHost")
|
||||
private StringProperty proxyHost = new SimpleStringProperty();
|
||||
|
||||
private String proxyHost;
|
||||
@SerializedName("proxyPort")
|
||||
private IntegerProperty proxyPort = new SimpleIntegerProperty();
|
||||
|
||||
private String proxyPort;
|
||||
@SerializedName("proxyUserName")
|
||||
private StringProperty proxyUser = new SimpleStringProperty();
|
||||
|
||||
private String proxyUserName;
|
||||
@SerializedName("proxyPassword")
|
||||
private StringProperty proxyPass = new SimpleStringProperty();
|
||||
|
||||
private String proxyPassword;
|
||||
@SerializedName("proxyType")
|
||||
private int proxyType;
|
||||
@SerializedName("enableShadow")
|
||||
private boolean enableShadow;
|
||||
@SerializedName("enableBlur")
|
||||
private boolean enableBlur;
|
||||
@SerializedName("enableAnimation")
|
||||
private boolean enableAnimation;
|
||||
@SerializedName("decorated")
|
||||
private boolean decorated;
|
||||
@SerializedName("theme")
|
||||
private ObjectProperty<Theme> theme = new SimpleObjectProperty<>(Theme.BLUE);
|
||||
|
||||
private String theme;
|
||||
@SerializedName("java")
|
||||
private List<JdkVersion> java;
|
||||
@SerializedName("localization")
|
||||
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
|
||||
|
||||
@SerializedName("downloadType")
|
||||
private StringProperty downloadType = new SimpleStringProperty("bmclapi");
|
||||
|
||||
private String localization;
|
||||
@SerializedName("logintype")
|
||||
private int logintype;
|
||||
@SerializedName("downloadtype")
|
||||
private int downloadtype;
|
||||
@SerializedName("configurations")
|
||||
private ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
|
||||
|
||||
@SerializedName("accounts")
|
||||
private ObservableList<Map<Object, Object>> accountStorages = FXCollections.observableArrayList();
|
||||
|
||||
private TreeMap<String, Profile> configurations;
|
||||
@SerializedName("auth")
|
||||
private Map<String, Map> auth;
|
||||
@SerializedName("fontFamily")
|
||||
private StringProperty fontFamily = new SimpleStringProperty("Consolas");
|
||||
|
||||
private String fontFamily;
|
||||
@SerializedName("fontSize")
|
||||
private DoubleProperty fontSize = new SimpleDoubleProperty(12);
|
||||
|
||||
private int fontSize;
|
||||
@SerializedName("logLines")
|
||||
private IntegerProperty logLines = new SimpleIntegerProperty(100);
|
||||
|
||||
@SerializedName("authlibInjectorServers")
|
||||
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList(server -> new Observable[] { server });
|
||||
|
||||
@SerializedName("updateChannel")
|
||||
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);
|
||||
|
||||
@SerializedName("_version")
|
||||
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
|
||||
|
||||
/**
|
||||
* The version of UI that the user have last used.
|
||||
* If there is a major change in UI, {@link Config#CURRENT_UI_VERSION} should be increased.
|
||||
* When {@link #CURRENT_UI_VERSION} is higher than the property, the user guide should be shown,
|
||||
* then this property is set to the same value as {@link #CURRENT_UI_VERSION}.
|
||||
* In particular, the property is default to 0, so that whoever open the application for the first time will see the guide.
|
||||
*/
|
||||
@SerializedName("uiVersion")
|
||||
private IntegerProperty uiVersion = new SimpleIntegerProperty(0);
|
||||
|
||||
/**
|
||||
* The preferred login type to use when the user wants to add an account.
|
||||
*/
|
||||
@SerializedName("preferredLoginType")
|
||||
private StringProperty preferredLoginType = new SimpleStringProperty();
|
||||
|
||||
private transient ObservableHelper helper = new ObservableHelper(this);
|
||||
private int logLines;
|
||||
|
||||
public Config() {
|
||||
PropertyUtils.attachListener(this, helper);
|
||||
clientToken = UUID.randomUUID().toString();
|
||||
proxyType = logintype = downloadtype = 0;
|
||||
logLines = 100;
|
||||
enableAnimation = enableBlur = true;
|
||||
if (OS.os() == OS.WINDOWS)
|
||||
enableShadow = true;
|
||||
theme = LAFTheme.BLUE.id;
|
||||
decorated = OS.os() == OS.LINUX;
|
||||
auth = new HashMap<>();
|
||||
Font font = Font.decode("Consolas");
|
||||
if (font == null)
|
||||
font = Font.decode("Monospace");
|
||||
if (font != null)
|
||||
fontFamily = font.getFamily();
|
||||
fontSize = 12;
|
||||
commonpath = MCUtils.getLocation().getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
helper.addListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
helper.removeListener(listener);
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
return CONFIG_GSON.toJson(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Config clone() {
|
||||
return fromJson(this.toJson());
|
||||
}
|
||||
|
||||
// Getters & Setters & Properties
|
||||
public String getSelectedProfile() {
|
||||
return selectedProfile.get();
|
||||
}
|
||||
|
||||
public void setSelectedProfile(String selectedProfile) {
|
||||
this.selectedProfile.set(selectedProfile);
|
||||
}
|
||||
|
||||
public StringProperty selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
public EnumBackgroundImage getBackgroundImageType() {
|
||||
return backgroundImageType.get();
|
||||
}
|
||||
|
||||
public void setBackgroundImageType(EnumBackgroundImage backgroundImageType) {
|
||||
this.backgroundImageType.set(backgroundImageType);
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {
|
||||
return backgroundImageType;
|
||||
}
|
||||
|
||||
public String getBackgroundImage() {
|
||||
return backgroundImage.get();
|
||||
}
|
||||
|
||||
public void setBackgroundImage(String backgroundImage) {
|
||||
this.backgroundImage.set(backgroundImage);
|
||||
}
|
||||
|
||||
public StringProperty backgroundImageProperty() {
|
||||
return backgroundImage;
|
||||
}
|
||||
|
||||
public EnumCommonDirectory getCommonDirType() {
|
||||
return commonDirType.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EnumCommonDirectory> commonDirTypeProperty() {
|
||||
return commonDirType;
|
||||
}
|
||||
|
||||
public void setCommonDirType(EnumCommonDirectory commonDirType) {
|
||||
this.commonDirType.set(commonDirType);
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
return commonDirectory.get();
|
||||
}
|
||||
|
||||
public void setCommonDirectory(String commonDirectory) {
|
||||
this.commonDirectory.set(commonDirectory);
|
||||
}
|
||||
|
||||
public StringProperty commonDirectoryProperty() {
|
||||
return commonDirectory;
|
||||
}
|
||||
|
||||
public boolean hasProxy() {
|
||||
return hasProxy.get();
|
||||
}
|
||||
|
||||
public void setHasProxy(boolean hasProxy) {
|
||||
this.hasProxy.set(hasProxy);
|
||||
}
|
||||
|
||||
public BooleanProperty hasProxyProperty() {
|
||||
return hasProxy;
|
||||
}
|
||||
|
||||
public boolean hasProxyAuth() {
|
||||
return hasProxyAuth.get();
|
||||
}
|
||||
|
||||
public void setHasProxyAuth(boolean hasProxyAuth) {
|
||||
this.hasProxyAuth.set(hasProxyAuth);
|
||||
}
|
||||
|
||||
public BooleanProperty hasProxyAuthProperty() {
|
||||
return hasProxyAuth;
|
||||
}
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
return proxyType.get();
|
||||
}
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType.set(proxyType);
|
||||
}
|
||||
|
||||
public ObjectProperty<Proxy.Type> proxyTypeProperty() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public String getProxyHost() {
|
||||
return proxyHost.get();
|
||||
}
|
||||
|
||||
public void setProxyHost(String proxyHost) {
|
||||
this.proxyHost.set(proxyHost);
|
||||
}
|
||||
|
||||
public StringProperty proxyHostProperty() {
|
||||
return proxyHost;
|
||||
}
|
||||
|
||||
public int getProxyPort() {
|
||||
return proxyPort.get();
|
||||
}
|
||||
|
||||
public void setProxyPort(int proxyPort) {
|
||||
this.proxyPort.set(proxyPort);
|
||||
}
|
||||
|
||||
public IntegerProperty proxyPortProperty() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
public String getProxyUser() {
|
||||
return proxyUser.get();
|
||||
}
|
||||
|
||||
public void setProxyUser(String proxyUser) {
|
||||
this.proxyUser.set(proxyUser);
|
||||
}
|
||||
|
||||
public StringProperty proxyUserProperty() {
|
||||
return proxyUser;
|
||||
}
|
||||
|
||||
public String getProxyPass() {
|
||||
return proxyPass.get();
|
||||
}
|
||||
|
||||
public void setProxyPass(String proxyPass) {
|
||||
this.proxyPass.set(proxyPass);
|
||||
}
|
||||
|
||||
public StringProperty proxyPassProperty() {
|
||||
return proxyPass;
|
||||
public List<JdkVersion> getJava() {
|
||||
return java == null ? java = new ArrayList<>() : java;
|
||||
}
|
||||
|
||||
public Theme getTheme() {
|
||||
return theme.get();
|
||||
if (!Theme.THEMES.containsKey(theme))
|
||||
theme = LAFTheme.BLUE.id;
|
||||
return Theme.THEMES.get(theme);
|
||||
}
|
||||
|
||||
public void setTheme(Theme theme) {
|
||||
this.theme.set(theme);
|
||||
public void setTheme(String theme) {
|
||||
this.theme = theme;
|
||||
HMCLApi.EVENT_BUS.fireChannel(new ThemeChangedEvent(this, getTheme()));
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public ObjectProperty<Theme> themeProperty() {
|
||||
return theme;
|
||||
public boolean isDecorated() {
|
||||
return decorated;
|
||||
}
|
||||
|
||||
public SupportedLocale getLocalization() {
|
||||
return localization.get();
|
||||
public void setDecorated(boolean decorated) {
|
||||
this.decorated = decorated;
|
||||
}
|
||||
|
||||
public void setLocalization(SupportedLocale localization) {
|
||||
this.localization.set(localization);
|
||||
public boolean isEnableShadow() {
|
||||
return enableShadow && OS.os() == OS.WINDOWS;
|
||||
}
|
||||
|
||||
public ObjectProperty<SupportedLocale> localizationProperty() {
|
||||
return localization;
|
||||
public void setEnableShadow(boolean enableShadow) {
|
||||
this.enableShadow = enableShadow;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public String getDownloadType() {
|
||||
return downloadType.get();
|
||||
public boolean isEnableAnimation() {
|
||||
return enableAnimation;
|
||||
}
|
||||
|
||||
public void setDownloadType(String downloadType) {
|
||||
this.downloadType.set(downloadType);
|
||||
public void setEnableAnimation(boolean enableAnimation) {
|
||||
this.enableAnimation = enableAnimation;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public StringProperty downloadTypeProperty() {
|
||||
return downloadType;
|
||||
public boolean isEnableBlur() {
|
||||
return enableBlur;
|
||||
}
|
||||
|
||||
public ObservableMap<String, Profile> getConfigurations() {
|
||||
public void setEnableBlur(boolean enableBlur) {
|
||||
this.enableBlur = enableBlur;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Last selected profile name.
|
||||
*/
|
||||
public String getLast() {
|
||||
if (last == null)
|
||||
last = Settings.DEFAULT_PROFILE;
|
||||
return last;
|
||||
}
|
||||
|
||||
public void setLast(String last) {
|
||||
this.last = last;
|
||||
Settings.onProfileChanged();
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public String getBgpath() {
|
||||
return bgpath;
|
||||
}
|
||||
|
||||
public void setBgpath(String bgpath) {
|
||||
this.bgpath = bgpath;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public String getCommonpath() {
|
||||
return commonpath;
|
||||
}
|
||||
|
||||
public void setCommonpath(String commonpath) {
|
||||
this.commonpath = commonpath;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public String getClientToken() {
|
||||
return clientToken;
|
||||
}
|
||||
|
||||
public IAuthenticator getAuthenticator() {
|
||||
return AbstractAuthenticator.LOGINS.get(getLoginType());
|
||||
}
|
||||
|
||||
public int getLoginType() {
|
||||
if (logintype < 0 || logintype >= AbstractAuthenticator.LOGINS.size())
|
||||
logintype = 0;
|
||||
return logintype;
|
||||
}
|
||||
|
||||
public void setLoginType(int logintype) {
|
||||
if (logintype < 0 || logintype >= AbstractAuthenticator.LOGINS.size())
|
||||
return;
|
||||
this.logintype = logintype;
|
||||
HMCLApi.EVENT_BUS.fireChannel(new AuthenticatorChangedEvent(this, AbstractAuthenticator.LOGINS.get(logintype)));
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public int getDownloadType() {
|
||||
return downloadtype;
|
||||
}
|
||||
|
||||
public void setDownloadType(int downloadtype) {
|
||||
this.downloadtype = downloadtype;
|
||||
HMCLApi.EVENT_BUS.fireChannel(new DownloadTypeChangedEvent(this, getDownloadSource().name()));
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public TreeMap<String, Profile> getConfigurations() {
|
||||
if (configurations == null)
|
||||
configurations = new TreeMap<>();
|
||||
if (configurations.isEmpty()) {
|
||||
Profile profile = new Profile();
|
||||
configurations.put(profile.getName(), profile);
|
||||
}
|
||||
return configurations;
|
||||
}
|
||||
|
||||
public ObservableList<Map<Object, Object>> getAccountStorages() {
|
||||
return accountStorages;
|
||||
public Map getAuthenticatorConfig(String authId) {
|
||||
return auth.get(authId);
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return fontFamily.get();
|
||||
public void setAuthenticatorConfig(String authId, Map map) {
|
||||
auth.put(authId, map);
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public void setFontFamily(String fontFamily) {
|
||||
this.fontFamily.set(fontFamily);
|
||||
public DownloadType getDownloadSource() {
|
||||
if (downloadtype >= DownloadType.values().length || downloadtype < 0) {
|
||||
downloadtype = 0;
|
||||
Settings.save();
|
||||
}
|
||||
return DownloadType.values()[downloadtype];
|
||||
}
|
||||
|
||||
public StringProperty fontFamilyProperty() {
|
||||
return fontFamily;
|
||||
public String getProxyHost() {
|
||||
return proxyHost == null ? "" : proxyHost;
|
||||
}
|
||||
|
||||
public double getFontSize() {
|
||||
return fontSize.get();
|
||||
public void setProxyHost(String proxyHost) {
|
||||
this.proxyHost = proxyHost;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public void setFontSize(double fontSize) {
|
||||
this.fontSize.set(fontSize);
|
||||
public String getProxyPort() {
|
||||
return proxyPort == null ? "" : proxyPort;
|
||||
}
|
||||
|
||||
public DoubleProperty fontSizeProperty() {
|
||||
public void setProxyPort(String proxyPort) {
|
||||
this.proxyPort = proxyPort;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public Proxy.Type getProxyType() {
|
||||
if (proxyType < 0 || proxyType >= 3) {
|
||||
proxyType = 0;
|
||||
Settings.save();
|
||||
}
|
||||
return Proxy.Type.values()[proxyType];
|
||||
}
|
||||
|
||||
public void setProxyType(Proxy.Type proxyType) {
|
||||
this.proxyType = proxyType.ordinal();
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public String getProxyUserName() {
|
||||
return proxyUserName == null ? "" : proxyUserName;
|
||||
}
|
||||
|
||||
public void setProxyUserName(String proxyUserName) {
|
||||
this.proxyUserName = proxyUserName;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public String getProxyPassword() {
|
||||
return proxyPassword == null ? "" : proxyPassword;
|
||||
}
|
||||
|
||||
public void setProxyPassword(String proxyPassword) {
|
||||
this.proxyPassword = proxyPassword;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public int getFontSize() {
|
||||
return fontSize;
|
||||
}
|
||||
|
||||
public void setFontSize(int fontSize) {
|
||||
this.fontSize = fontSize;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public Font getConsoleFont() {
|
||||
return Font.decode(fontFamily + "-" + fontSize);
|
||||
}
|
||||
|
||||
public String getFontFamily() {
|
||||
return Font.decode(fontFamily).getFamily();
|
||||
}
|
||||
|
||||
public void setFontFamily(String fontFamily) {
|
||||
this.fontFamily = Font.decode(fontFamily).getFamily(); // avoid invalid font family
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public String getLocalization() {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public void setLocalization(String localization) {
|
||||
this.localization = localization;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public int getLogLines() {
|
||||
return logLines.get();
|
||||
if (logLines == 100 || logLines == 1000 || logLines == 5000)
|
||||
return logLines;
|
||||
else
|
||||
return logLines = 100;
|
||||
}
|
||||
|
||||
public void setLogLines(int logLines) {
|
||||
this.logLines.set(logLines);
|
||||
this.logLines = logLines;
|
||||
Settings.save();
|
||||
}
|
||||
|
||||
public IntegerProperty logLinesProperty() {
|
||||
return logLines;
|
||||
@Override
|
||||
public Object clone() {
|
||||
try {
|
||||
return super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableList<AuthlibInjectorServer> getAuthlibInjectorServers() {
|
||||
return authlibInjectorServers;
|
||||
}
|
||||
|
||||
public UpdateChannel getUpdateChannel() {
|
||||
return updateChannel.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<UpdateChannel> updateChannelProperty() {
|
||||
return updateChannel;
|
||||
}
|
||||
|
||||
public void setUpdateChannel(UpdateChannel updateChannel) {
|
||||
this.updateChannel.set(updateChannel);
|
||||
}
|
||||
|
||||
public int getConfigVersion() {
|
||||
return configVersion.get();
|
||||
}
|
||||
|
||||
public IntegerProperty configVersionProperty() {
|
||||
return configVersion;
|
||||
}
|
||||
|
||||
public void setConfigVersion(int configVersion) {
|
||||
this.configVersion.set(configVersion);
|
||||
}
|
||||
|
||||
public int getUiVersion() {
|
||||
return uiVersion.get();
|
||||
}
|
||||
|
||||
public IntegerProperty uiVersionProperty() {
|
||||
return uiVersion;
|
||||
}
|
||||
|
||||
public void setUiVersion(int uiVersion) {
|
||||
this.uiVersion.set(uiVersion);
|
||||
}
|
||||
|
||||
public String getPreferredLoginType() {
|
||||
return preferredLoginType.get();
|
||||
}
|
||||
|
||||
public void setPreferredLoginType(String preferredLoginType) {
|
||||
this.preferredLoginType.set(preferredLoginType);
|
||||
}
|
||||
|
||||
public StringProperty preferredLoginTypeProperty() {
|
||||
return preferredLoginType;
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParseException;
|
||||
import org.jackhuang.hmcl.util.InvocationDispatcher;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class ConfigHolder {
|
||||
|
||||
private ConfigHolder() {}
|
||||
|
||||
public static final String CONFIG_FILENAME = "hmcl.json";
|
||||
public static final String CONFIG_FILENAME_LINUX = ".hmcl.json";
|
||||
|
||||
private static Path configLocation;
|
||||
private static Config configInstance;
|
||||
private static boolean newlyCreated;
|
||||
|
||||
public static Config config() {
|
||||
if (configInstance == null) {
|
||||
throw new IllegalStateException("Configuration hasn't been loaded");
|
||||
}
|
||||
return configInstance;
|
||||
}
|
||||
|
||||
public synchronized static void init() throws IOException {
|
||||
if (configInstance != null) {
|
||||
throw new IllegalStateException("Configuration is already loaded");
|
||||
}
|
||||
|
||||
configLocation = locateConfig();
|
||||
configInstance = loadConfig();
|
||||
configInstance.addListener(source -> markConfigDirty());
|
||||
|
||||
Settings.init();
|
||||
|
||||
if (newlyCreated) {
|
||||
saveConfigSync();
|
||||
|
||||
// hide the config file on windows
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
|
||||
try {
|
||||
Files.setAttribute(configLocation, "dos:hidden", true);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.WARNING, "Failed to set hidden attribute of " + configLocation, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Files.isWritable(configLocation)) {
|
||||
// the config cannot be saved
|
||||
// throw up the error now to prevent further data loss
|
||||
throw new IOException("Config at " + configLocation + " is not writable");
|
||||
}
|
||||
}
|
||||
|
||||
private static Path locateConfig() {
|
||||
Path config = Paths.get(CONFIG_FILENAME);
|
||||
if (Files.isRegularFile(config))
|
||||
return config;
|
||||
|
||||
Path dotConfig = Paths.get(CONFIG_FILENAME_LINUX);
|
||||
if (Files.isRegularFile(dotConfig))
|
||||
return dotConfig;
|
||||
|
||||
// create new
|
||||
return OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? config : dotConfig;
|
||||
}
|
||||
|
||||
private static Config loadConfig() throws IOException {
|
||||
if (Files.exists(configLocation)) {
|
||||
try {
|
||||
String content = FileUtils.readText(configLocation);
|
||||
Config deserialized = Config.fromJson(content);
|
||||
if (deserialized == null) {
|
||||
LOG.info("Config is empty");
|
||||
} else {
|
||||
Map<?, ?> raw = new Gson().fromJson(content, Map.class);
|
||||
ConfigUpgrader.upgradeConfig(deserialized, raw);
|
||||
return deserialized;
|
||||
}
|
||||
} catch (JsonParseException e) {
|
||||
LOG.log(Level.WARNING, "Malformed config.", e);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Creating an empty config");
|
||||
newlyCreated = true;
|
||||
return new Config();
|
||||
}
|
||||
|
||||
private static InvocationDispatcher<String> configWriter = InvocationDispatcher.runOn(Lang::thread, content -> {
|
||||
try {
|
||||
writeToConfig(content);
|
||||
} catch (IOException e) {
|
||||
LOG.log(Level.SEVERE, "Failed to save config", e);
|
||||
}
|
||||
});
|
||||
|
||||
private static void writeToConfig(String content) throws IOException {
|
||||
LOG.info("Saving config");
|
||||
synchronized (configLocation) {
|
||||
Files.write(configLocation, content.getBytes(UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
static void markConfigDirty() {
|
||||
configWriter.accept(configInstance.toJson());
|
||||
}
|
||||
|
||||
private static void saveConfigSync() throws IOException {
|
||||
writeToConfig(configInstance.toJson());
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
final class ConfigUpgrader {
|
||||
private static final int VERSION = 0;
|
||||
|
||||
private ConfigUpgrader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is for the compatibility with old HMCL 3.x as well as HMCL 2.x.
|
||||
*
|
||||
* @param deserialized deserialized config settings
|
||||
* @param rawJson raw json structure of the config settings without modification
|
||||
* @return true if config version is upgraded
|
||||
*/
|
||||
static boolean upgradeConfig(Config deserialized, Map<?, ?> rawJson) {
|
||||
boolean upgraded;
|
||||
if (deserialized.getConfigVersion() < VERSION) {
|
||||
deserialized.setConfigVersion(VERSION);
|
||||
// TODO: Add upgrade code here.
|
||||
upgraded = true;
|
||||
} else {
|
||||
upgraded = false;
|
||||
}
|
||||
|
||||
upgradeV2(deserialized, rawJson);
|
||||
upgradeV3(deserialized, rawJson);
|
||||
|
||||
return upgraded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade configuration of HMCL 2.x
|
||||
*
|
||||
* @param deserialized deserialized config settings
|
||||
* @param rawJson raw json structure of the config settings without modification
|
||||
*/
|
||||
private static void upgradeV2(Config deserialized, Map<?, ?> rawJson) {
|
||||
// Convert OfflineAccounts whose stored uuid is important.
|
||||
tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
|
||||
tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
|
||||
String selected = rawJson.containsKey("selectedAccount") ? null
|
||||
: tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null);
|
||||
|
||||
tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
|
||||
((Map<?, ?>) uuidMap).forEach((key, value) -> {
|
||||
Map<Object, Object> storage = new HashMap<>();
|
||||
storage.put("type", "offline");
|
||||
storage.put("username", key);
|
||||
storage.put("uuid", value);
|
||||
if (key.equals(selected)) {
|
||||
storage.put("selected", true);
|
||||
}
|
||||
deserialized.getAccountStorages().add(storage);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upgrade configuration of HMCL earlier than 3.1.70
|
||||
*
|
||||
* @param deserialized deserialized config settings
|
||||
* @param rawJson raw json structure of the config settings without modification
|
||||
*/
|
||||
private static void upgradeV3(Config deserialized, Map<?, ?> rawJson) {
|
||||
if (!rawJson.containsKey("commonDirType"))
|
||||
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
|
||||
if (!rawJson.containsKey("backgroundType"))
|
||||
deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
|
||||
if (!rawJson.containsKey("hasProxy"))
|
||||
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
|
||||
if (!rawJson.containsKey("hasProxyAuth"))
|
||||
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
|
||||
|
||||
if (!rawJson.containsKey("downloadType")) {
|
||||
tryCast(rawJson.get("downloadtype"), Number.class)
|
||||
.map(Number::intValue)
|
||||
.ifPresent(id -> {
|
||||
if (id == 0) {
|
||||
deserialized.setDownloadType("mojang");
|
||||
} else if (id == 1) {
|
||||
deserialized.setDownloadType("bmclapi");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tryCast(rawJson.get("selectedAccount"), String.class)
|
||||
.ifPresent(selected -> {
|
||||
deserialized.getAccountStorages().stream()
|
||||
.filter(storage -> {
|
||||
Object type = storage.get("type");
|
||||
if ("offline".equals(type)) {
|
||||
return selected.equals(storage.get("username") + ":" + storage.get("username"));
|
||||
} else if ("yggdrasil".equals(type) || "authlibInjector".equals(type)) {
|
||||
return selected.equals(storage.get("username") + ":" + storage.get("displayName"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.findFirst()
|
||||
.ifPresent(storage -> storage.put("selected", true));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.mapOf;
|
||||
import static org.jackhuang.hmcl.util.Pair.pair;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
|
||||
import org.jackhuang.hmcl.download.DownloadProvider;
|
||||
import org.jackhuang.hmcl.download.MojangDownloadProvider;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
|
||||
public final class DownloadProviders {
|
||||
private DownloadProviders() {}
|
||||
|
||||
public static final Map<String, DownloadProvider> providersById = mapOf(
|
||||
pair("mojang", new MojangDownloadProvider()),
|
||||
pair("bmclapi", new BMCLAPIDownloadProvider()));
|
||||
|
||||
public static final String DEFAULT_PROVIDER_ID = "bmclapi";
|
||||
|
||||
private static ObjectBinding<DownloadProvider> downloadProviderProperty;
|
||||
|
||||
static void init() {
|
||||
downloadProviderProperty = Bindings.createObjectBinding(
|
||||
() -> Optional.ofNullable(providersById.get(config().getDownloadType()))
|
||||
.orElse(providersById.get(DEFAULT_PROVIDER_ID)),
|
||||
config().downloadTypeProperty());
|
||||
}
|
||||
|
||||
public static DownloadProvider getDownloadProvider() {
|
||||
return downloadProviderProperty.get();
|
||||
}
|
||||
|
||||
public static ObservableObjectValue<DownloadProvider> downloadProviderProperty() {
|
||||
return downloadProviderProperty;
|
||||
}
|
||||
}
|
||||
Regular → Executable
+124
-183
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -13,229 +13,170 @@
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import org.jackhuang.hmcl.download.DefaultDependencyManager;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.EventPriority;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.ui.WeakListenerHolder;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
|
||||
|
||||
import org.jackhuang.hmcl.core.service.IProfile;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.jackhuang.hmcl.util.HMCLGameLauncher;
|
||||
import org.jackhuang.hmcl.util.HMCLMinecraftService;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import org.jackhuang.hmcl.core.MCUtils;
|
||||
import org.jackhuang.hmcl.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.api.event.EventHandler;
|
||||
import org.jackhuang.hmcl.api.event.PropertyChangedEvent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@JsonAdapter(Profile.Serializer.class)
|
||||
public final class Profile implements Observable {
|
||||
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
|
||||
private final HMCLGameRepository repository;
|
||||
public final class Profile implements IProfile {
|
||||
|
||||
private final StringProperty selectedVersion = new SimpleStringProperty();
|
||||
@SerializedName("selectedMinecraftVersion")
|
||||
private String selectedVersion = "";
|
||||
@SerializedName("gameDir")
|
||||
private String gameDir;
|
||||
@SerializedName("noCommon")
|
||||
private boolean noCommon;
|
||||
@SerializedName("global")
|
||||
private VersionSetting global;
|
||||
|
||||
public StringProperty selectedVersionProperty() {
|
||||
return selectedVersion;
|
||||
}
|
||||
private transient String name;
|
||||
private transient HMCLMinecraftService service = new HMCLMinecraftService(this);
|
||||
private transient HMCLGameLauncher launcher = new HMCLGameLauncher(this);
|
||||
public transient final EventHandler<PropertyChangedEvent<String>> propertyChanged = new EventHandler<>();
|
||||
|
||||
public String getSelectedVersion() {
|
||||
return selectedVersion.get();
|
||||
}
|
||||
|
||||
public void setSelectedVersion(String selectedVersion) {
|
||||
this.selectedVersion.set(selectedVersion);
|
||||
}
|
||||
|
||||
private final ObjectProperty<File> gameDir;
|
||||
|
||||
public ObjectProperty<File> gameDirProperty() {
|
||||
return gameDir;
|
||||
}
|
||||
|
||||
public File getGameDir() {
|
||||
return gameDir.get();
|
||||
}
|
||||
|
||||
public void setGameDir(File gameDir) {
|
||||
this.gameDir.set(gameDir);
|
||||
}
|
||||
|
||||
private final ReadOnlyObjectWrapper<VersionSetting> global = new ReadOnlyObjectWrapper<>(this, "global");
|
||||
|
||||
public ReadOnlyObjectProperty<VersionSetting> globalProperty() {
|
||||
return global.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public VersionSetting getGlobal() {
|
||||
return global.get();
|
||||
}
|
||||
|
||||
private final SimpleStringProperty name;
|
||||
|
||||
public StringProperty nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name.get();
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name.set(name);
|
||||
}
|
||||
|
||||
private BooleanProperty useRelativePath = new SimpleBooleanProperty(this, "useRelativePath", false);
|
||||
|
||||
public BooleanProperty useRelativePathProperty() {
|
||||
return useRelativePath;
|
||||
}
|
||||
|
||||
public boolean isUseRelativePath() {
|
||||
return useRelativePath.get();
|
||||
}
|
||||
|
||||
public void setUseRelativePath(boolean useRelativePath) {
|
||||
this.useRelativePath.set(useRelativePath);
|
||||
public Profile() {
|
||||
this("Default");
|
||||
}
|
||||
|
||||
public Profile(String name) {
|
||||
this(name, new File(".minecraft"));
|
||||
this(name, ".minecraft");
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir) {
|
||||
this(name, initialGameDir, new VersionSetting());
|
||||
public Profile(String name, String gameDir) {
|
||||
this.name = name;
|
||||
this.gameDir = gameDir;
|
||||
this.global = new VersionSetting();
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir, VersionSetting global) {
|
||||
this(name, initialGameDir, global, null, false);
|
||||
public Profile(String name, Profile v) {
|
||||
this();
|
||||
if (v == null)
|
||||
return;
|
||||
this.name = name;
|
||||
gameDir = v.gameDir;
|
||||
}
|
||||
|
||||
public Profile(String name, File initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) {
|
||||
this.name = new SimpleStringProperty(this, "name", name);
|
||||
gameDir = new SimpleObjectProperty<>(this, "gameDir", initialGameDir);
|
||||
repository = new HMCLGameRepository(this, initialGameDir);
|
||||
this.global.set(global == null ? new VersionSetting() : global);
|
||||
this.selectedVersion.set(selectedVersion);
|
||||
this.useRelativePath.set(useRelativePath);
|
||||
|
||||
gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue));
|
||||
this.selectedVersion.addListener(o -> checkSelectedVersion());
|
||||
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
|
||||
|
||||
addPropertyChangedListener(onInvalidating(this::invalidate));
|
||||
@Override
|
||||
public HMCLMinecraftService service() {
|
||||
return service;
|
||||
}
|
||||
|
||||
private void checkSelectedVersion() {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (!repository.isLoaded()) return;
|
||||
String newValue = selectedVersion.get();
|
||||
if (!repository.hasVersion(newValue)) {
|
||||
Optional<String> version = repository.getVersions().stream().findFirst().map(Version::getId);
|
||||
if (version.isPresent())
|
||||
selectedVersion.setValue(version.get());
|
||||
else if (newValue != null)
|
||||
selectedVersion.setValue(null);
|
||||
}
|
||||
});
|
||||
public HMCLGameLauncher launcher() {
|
||||
return launcher;
|
||||
}
|
||||
|
||||
public HMCLGameRepository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public DefaultDependencyManager getDependency() {
|
||||
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
|
||||
public VersionSetting getSelectedVersionSetting() {
|
||||
return getVersionSetting(getSelectedVersion());
|
||||
}
|
||||
|
||||
public VersionSetting getVersionSetting(String id) {
|
||||
VersionSetting vs = repository.getVersionSetting(id);
|
||||
VersionSetting vs = service().getVersionSetting(id);
|
||||
if (vs == null || vs.isUsesGlobal()) {
|
||||
getGlobal().setGlobal(true); // always keep global.isGlobal = true
|
||||
getGlobal().setUsesGlobal(true);
|
||||
return getGlobal();
|
||||
global.isGlobal = true;
|
||||
global.id = id;
|
||||
return global;
|
||||
} else
|
||||
return vs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new ToStringBuilder(this)
|
||||
.append("gameDir", getGameDir())
|
||||
.append("name", getName())
|
||||
.append("useRelativePath", isUseRelativePath())
|
||||
.toString();
|
||||
|
||||
public boolean isVersionSettingGlobe(String id) {
|
||||
VersionSetting vs = service().getVersionSetting(id);
|
||||
return vs == null || vs.isUsesGlobal();
|
||||
}
|
||||
|
||||
private void addPropertyChangedListener(InvalidationListener listener) {
|
||||
name.addListener(listener);
|
||||
global.addListener(listener);
|
||||
gameDir.addListener(listener);
|
||||
useRelativePath.addListener(listener);
|
||||
global.get().addPropertyChangedListener(listener);
|
||||
selectedVersion.addListener(listener);
|
||||
public void makeVersionSettingSpecial(String id) {
|
||||
VersionSetting vs = service().getVersionSetting(id);
|
||||
if (vs == null) {
|
||||
service().createVersionSetting(id);
|
||||
vs = service().getVersionSetting(id);
|
||||
if (vs == null)
|
||||
return;
|
||||
vs.setUsesGlobal(false);
|
||||
} else
|
||||
vs.setUsesGlobal(false);
|
||||
propertyChanged.fire(new PropertyChangedEvent<>(this, "selectedVersion", selectedVersion, selectedVersion));
|
||||
}
|
||||
|
||||
private ObservableHelper observableHelper = new ObservableHelper(this);
|
||||
|
||||
@Override
|
||||
public void addListener(InvalidationListener listener) {
|
||||
observableHelper.addListener(listener);
|
||||
public void makeVersionSettingGlobal(String id) {
|
||||
VersionSetting vs = service().getVersionSetting(id);
|
||||
if (vs == null)
|
||||
return;
|
||||
vs.setUsesGlobal(true);
|
||||
propertyChanged.fire(new PropertyChangedEvent<>(this, "selectedVersion", selectedVersion, selectedVersion));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(InvalidationListener listener) {
|
||||
observableHelper.removeListener(listener);
|
||||
}
|
||||
|
||||
private void invalidate() {
|
||||
Platform.runLater(observableHelper::invalidate);
|
||||
}
|
||||
|
||||
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
|
||||
@Override
|
||||
public JsonElement serialize(Profile src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
if (src == null)
|
||||
return JsonNull.INSTANCE;
|
||||
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
jsonObject.add("global", context.serialize(src.getGlobal()));
|
||||
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
|
||||
jsonObject.addProperty("useRelativePath", src.isUseRelativePath());
|
||||
jsonObject.addProperty("selectedMinecraftVersion", src.getSelectedVersion());
|
||||
|
||||
return jsonObject;
|
||||
public String getSelectedVersion() {
|
||||
String v = selectedVersion;
|
||||
if (StrUtils.isBlank(v) || service().version().getVersionById(v) == null || service().version().getVersionById(v).hidden) {
|
||||
MinecraftVersion mv = service().version().getOneVersion(t -> !t.hidden);
|
||||
if (mv != null)
|
||||
v = mv.id;
|
||||
if (StrUtils.isNotBlank(v))
|
||||
setSelectedVersion(v);
|
||||
}
|
||||
return StrUtils.isBlank(v) ? null : v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Profile deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (json == null || json == JsonNull.INSTANCE || !(json instanceof JsonObject)) return null;
|
||||
JsonObject obj = (JsonObject) json;
|
||||
String gameDir = Optional.ofNullable(obj.get("gameDir")).map(JsonElement::getAsString).orElse("");
|
||||
@Override
|
||||
public void setSelectedVersion(String newVersion) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent<>(this, "selectedVersion", this.selectedVersion, newVersion);
|
||||
this.selectedVersion = newVersion;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
return new Profile("Default",
|
||||
new File(gameDir),
|
||||
context.deserialize(obj.get("global"), VersionSetting.class),
|
||||
Optional.ofNullable(obj.get("selectedMinecraftVersion")).map(JsonElement::getAsString).orElse(""),
|
||||
Optional.ofNullable(obj.get("useRelativePath")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
}
|
||||
@Override
|
||||
public File getGameDir() {
|
||||
if (StrUtils.isBlank(gameDir))
|
||||
gameDir = MCUtils.getInitGameDir().getPath();
|
||||
return new File(gameDir.replace('\\', '/'));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGameDir(File gameDir) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent<>(this, "gameDir", this.gameDir, gameDir);
|
||||
this.gameDir = gameDir.getPath();
|
||||
service().version().refreshVersions();
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public boolean isNoCommon() {
|
||||
return noCommon;
|
||||
}
|
||||
|
||||
public void setNoCommon(boolean noCommon) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "noCommon", this.noCommon, noCommon);
|
||||
this.noCommon = noCommon;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
void setName(String name) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent<>(this, "name", this.name, name);
|
||||
this.name = name;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelected() {
|
||||
service().version().refreshVersions();
|
||||
}
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.ObservableList;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static javafx.collections.FXCollections.observableArrayList;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Profiles {
|
||||
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
|
||||
private Profiles() {
|
||||
}
|
||||
|
||||
public static String getProfileDisplayName(Profile profile) {
|
||||
switch (profile.getName()) {
|
||||
case Profiles.DEFAULT_PROFILE:
|
||||
return i18n("profile.default");
|
||||
case Profiles.HOME_PROFILE:
|
||||
return i18n("profile.home");
|
||||
default:
|
||||
return profile.getName();
|
||||
}
|
||||
}
|
||||
|
||||
private static final ObservableList<Profile> profiles = observableArrayList(profile -> new Observable[] { profile });
|
||||
private static final ReadOnlyListWrapper<Profile> profilesWrapper = new ReadOnlyListWrapper<>(profiles);
|
||||
|
||||
private static ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {
|
||||
{
|
||||
profiles.addListener(onInvalidating(this::invalidated));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
if (!initialized)
|
||||
return;
|
||||
|
||||
Profile profile = get();
|
||||
|
||||
if (profiles.isEmpty()) {
|
||||
if (profile != null) {
|
||||
set(null);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!profiles.contains(profile)) {
|
||||
set(profiles.get(0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
config().setSelectedProfile(profile == null ? "" : profile.getName());
|
||||
if (profile != null) {
|
||||
if (profile.getRepository().isLoaded())
|
||||
selectedVersion.bind(profile.selectedVersionProperty());
|
||||
else {
|
||||
selectedVersion.unbind();
|
||||
selectedVersion.set(null);
|
||||
// bind when repository was reloaded.
|
||||
profile.getRepository().refreshVersionsAsync().start();
|
||||
}
|
||||
} else {
|
||||
selectedVersion.unbind();
|
||||
selectedVersion.set(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static void checkProfiles() {
|
||||
if (profiles.isEmpty()) {
|
||||
Profile current = new Profile(Profiles.DEFAULT_PROFILE, new File(".minecraft"), new VersionSetting(), null, true);
|
||||
Profile home = new Profile(Profiles.HOME_PROFILE, Metadata.MINECRAFT_DIRECTORY.toFile());
|
||||
Platform.runLater(() -> profiles.addAll(current, home));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True if {@link #init()} hasn't been called.
|
||||
*/
|
||||
private static boolean initialized = false;
|
||||
|
||||
static {
|
||||
profiles.addListener(onInvalidating(Profiles::updateProfileStorages));
|
||||
profiles.addListener(onInvalidating(Profiles::checkProfiles));
|
||||
|
||||
selectedProfile.addListener((a, b, newValue) -> {
|
||||
if (newValue != null)
|
||||
newValue.getRepository().refreshVersionsAsync().start();
|
||||
});
|
||||
}
|
||||
|
||||
private static void updateProfileStorages() {
|
||||
// don't update the underlying storage before data loading is completed
|
||||
// otherwise it might cause data loss
|
||||
if (!initialized)
|
||||
return;
|
||||
// update storage
|
||||
config().getConfigurations().clear();
|
||||
config().getConfigurations().putAll(profiles.stream().collect(Collectors.toMap(Profile::getName, it -> it)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when it's ready to load profiles from {@link ConfigHolder#config()}.
|
||||
*/
|
||||
static void init() {
|
||||
if (initialized)
|
||||
throw new IllegalStateException("Already initialized");
|
||||
|
||||
HashSet<String> names = new HashSet<>();
|
||||
config().getConfigurations().forEach((name, profile) -> {
|
||||
if (!names.add(name)) return;
|
||||
profiles.add(profile);
|
||||
profile.setName(name);
|
||||
});
|
||||
checkProfiles();
|
||||
|
||||
// Platform.runLater is necessary or profiles will be empty
|
||||
// since checkProfiles adds 2 base profile later.
|
||||
Platform.runLater(() -> {
|
||||
initialized = true;
|
||||
|
||||
selectedProfile.set(
|
||||
profiles.stream()
|
||||
.filter(it -> it.getName().equals(config().getSelectedProfile()))
|
||||
.findFirst()
|
||||
.orElse(profiles.get(0)));
|
||||
});
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
Profile profile = selectedProfile.get();
|
||||
if (profile != null && profile.getRepository() == event.getSource()) {
|
||||
selectedVersion.bind(profile.selectedVersionProperty());
|
||||
for (Consumer<Profile> listener : versionsListeners)
|
||||
listener.accept(profile);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static ObservableList<Profile> getProfiles() {
|
||||
return profiles;
|
||||
}
|
||||
|
||||
public static ReadOnlyListProperty<Profile> profilesProperty() {
|
||||
return profilesWrapper.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public static Profile getSelectedProfile() {
|
||||
return selectedProfile.get();
|
||||
}
|
||||
|
||||
public static void setSelectedProfile(Profile profile) {
|
||||
selectedProfile.set(profile);
|
||||
}
|
||||
|
||||
public static ObjectProperty<Profile> selectedProfileProperty() {
|
||||
return selectedProfile;
|
||||
}
|
||||
|
||||
private static final ReadOnlyStringWrapper selectedVersion = new ReadOnlyStringWrapper();
|
||||
|
||||
public static ReadOnlyStringProperty selectedVersionProperty() {
|
||||
return selectedVersion.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
// Guaranteed that the repository is loaded.
|
||||
public static String getSelectedVersion() {
|
||||
return selectedVersion.get();
|
||||
}
|
||||
|
||||
private static final List<Consumer<Profile>> versionsListeners = new LinkedList<>();
|
||||
|
||||
public static void registerVersionsListener(Consumer<Profile> listener) {
|
||||
Profile profile = getSelectedProfile();
|
||||
if (profile != null && profile.getRepository().isLoaded())
|
||||
listener.accept(profile);
|
||||
versionsListeners.add(listener);
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.value.ObservableObjectValue;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.Proxy;
|
||||
import java.net.Proxy.Type;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public final class ProxyManager {
|
||||
private ProxyManager() {
|
||||
}
|
||||
|
||||
private static ObjectBinding<Proxy> proxyProperty;
|
||||
|
||||
public static Proxy getProxy() {
|
||||
return proxyProperty.get();
|
||||
}
|
||||
|
||||
public static ObservableObjectValue<Proxy> proxyProperty() {
|
||||
return proxyProperty;
|
||||
}
|
||||
|
||||
static void init() {
|
||||
proxyProperty = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
String host = config().getProxyHost();
|
||||
int port = config().getProxyPort();
|
||||
if (!config().hasProxy() || StringUtils.isBlank(host) || config().getProxyType() == Proxy.Type.DIRECT) {
|
||||
return Proxy.NO_PROXY;
|
||||
} else {
|
||||
if (port < 0 || port > 0xFFFF) {
|
||||
LOG.warning("Illegal proxy port: " + port);
|
||||
return Proxy.NO_PROXY;
|
||||
}
|
||||
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
|
||||
}
|
||||
},
|
||||
config().proxyTypeProperty(),
|
||||
config().proxyHostProperty(),
|
||||
config().proxyPortProperty(),
|
||||
config().hasProxyProperty());
|
||||
|
||||
proxyProperty.addListener(any -> updateSystemProxy());
|
||||
updateSystemProxy();
|
||||
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
if (config().hasProxyAuth()) {
|
||||
String username = config().getProxyUser();
|
||||
String password = config().getProxyPass();
|
||||
if (username != null && password != null) {
|
||||
return new PasswordAuthentication(username, password.toCharArray());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void updateSystemProxy() {
|
||||
Proxy proxy = proxyProperty.get();
|
||||
if (proxy.type() == Proxy.Type.DIRECT) {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
} else {
|
||||
InetSocketAddress address = (InetSocketAddress) proxy.address();
|
||||
String host = address.getHostString();
|
||||
String port = String.valueOf(address.getPort());
|
||||
if (proxy.type() == Type.HTTP) {
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
System.setProperty("http.proxyHost", host);
|
||||
System.setProperty("http.proxyPort", port);
|
||||
System.setProperty("https.proxyHost", host);
|
||||
System.setProperty("https.proxyPort", port);
|
||||
} else if (proxy.type() == Type.SOCKS) {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("https.proxyPort");
|
||||
System.setProperty("socksProxyHost", host);
|
||||
System.setProperty("socksProxyPort", port);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Regular → Executable
+145
-49
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 huangyuhui
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -13,64 +13,160 @@
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.HMCLCacheRepository;
|
||||
import org.jackhuang.hmcl.util.CacheRepository;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.core.MCUtils;
|
||||
import org.jackhuang.hmcl.core.download.DownloadType;
|
||||
import org.jackhuang.hmcl.util.CollectionUtils;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.api.event.config.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.api.event.config.ProfileLoadingEvent;
|
||||
import org.jackhuang.hmcl.laf.utils.AnimationController;
|
||||
import org.jackhuang.hmcl.util.sys.FileUtils;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
import org.jackhuang.hmcl.util.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.sys.IOUtils;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class Settings {
|
||||
|
||||
public class Settings {
|
||||
public static final String DEFAULT_PROFILE = "Default";
|
||||
public static final String HOME_PROFILE = "Home";
|
||||
|
||||
private static Settings instance;
|
||||
public static final File SETTINGS_FILE = new File("hmcl.json").getAbsoluteFile();
|
||||
|
||||
public static Settings instance() {
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("Settings hasn't been initialized");
|
||||
private static final Config SETTINGS;
|
||||
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(HMCLApi.HMCL_VERSION, "hmcl");
|
||||
|
||||
public static Config getInstance() {
|
||||
return SETTINGS;
|
||||
}
|
||||
|
||||
static {
|
||||
SETTINGS = initSettings();
|
||||
DownloadType.setSuggestedDownloadType(SETTINGS.getDownloadSource().name());
|
||||
AnimationController.ENABLE_ANIMATION = SETTINGS.isEnableAnimation();
|
||||
if (!getProfiles().containsKey(DEFAULT_PROFILE))
|
||||
getProfiles().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE));
|
||||
|
||||
for (Map.Entry<String, Profile> entry : getProfiles().entrySet()) {
|
||||
Profile e = entry.getValue();
|
||||
e.setName(entry.getKey());
|
||||
e.propertyChanged.register(Settings::save);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called from {@link ConfigHolder#init()}.
|
||||
*/
|
||||
static void init() {
|
||||
instance = new Settings();
|
||||
}
|
||||
|
||||
private Settings() {
|
||||
DownloadProviders.init();
|
||||
ProxyManager.init();
|
||||
Accounts.init();
|
||||
Profiles.init();
|
||||
|
||||
CacheRepository.setInstance(HMCLCacheRepository.REPOSITORY);
|
||||
HMCLCacheRepository.REPOSITORY.directoryProperty().bind(Bindings.createStringBinding(() -> {
|
||||
if (FileUtils.canCreateDirectory(getCommonDirectory())) {
|
||||
return getCommonDirectory();
|
||||
} else {
|
||||
return getDefaultCommonDirectory();
|
||||
private static Config initSettings() {
|
||||
Config c = new Config();
|
||||
if (SETTINGS_FILE.exists())
|
||||
try {
|
||||
String str = FileUtils.read(SETTINGS_FILE, IOUtils.DEFAULT_CHARSET);
|
||||
if (str == null || str.trim().equals(""))
|
||||
HMCLog.log("Settings file is empty, use the default settings.");
|
||||
else {
|
||||
Config d = C.GSON.fromJson(str, Config.class);
|
||||
if (d != null)
|
||||
c = d;
|
||||
}
|
||||
HMCLog.log("Initialized settings.");
|
||||
} catch (IOException | JsonSyntaxException e) {
|
||||
HMCLog.warn("Something happened wrongly when load settings.", e);
|
||||
}
|
||||
}, config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
}
|
||||
|
||||
public static String getDefaultCommonDirectory() {
|
||||
return Metadata.MINECRAFT_DIRECTORY.toString();
|
||||
}
|
||||
|
||||
public String getCommonDirectory() {
|
||||
switch (config().getCommonDirType()) {
|
||||
case DEFAULT:
|
||||
return getDefaultCommonDirectory();
|
||||
case CUSTOM:
|
||||
return config().getCommonDirectory();
|
||||
default:
|
||||
return null;
|
||||
else {
|
||||
HMCLog.log("No settings file here, may be first loading.");
|
||||
if (!c.getConfigurations().containsKey(HOME_PROFILE))
|
||||
c.getConfigurations().put(HOME_PROFILE, new Profile(HOME_PROFILE, MCUtils.getLocation().getPath()));
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
public static void save() {
|
||||
try {
|
||||
FileUtils.write(SETTINGS_FILE, C.GSON.toJson(SETTINGS), IOUtils.DEFAULT_CHARSET);
|
||||
} catch (IOException ex) {
|
||||
HMCLog.err("Failed to save config", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public static Profile getLastProfile() {
|
||||
if (!hasProfile(getInstance().getLast()))
|
||||
getInstance().setLast(DEFAULT_PROFILE);
|
||||
return getProfile(getInstance().getLast());
|
||||
}
|
||||
|
||||
public static Profile getProfile(String name) {
|
||||
if (name == null)
|
||||
name = DEFAULT_PROFILE;
|
||||
Profile p = getProfiles().get(name);
|
||||
if (p == null)
|
||||
if (getProfiles().containsKey(DEFAULT_PROFILE))
|
||||
p = getProfiles().get(DEFAULT_PROFILE);
|
||||
else
|
||||
getProfiles().put(DEFAULT_PROFILE, p = new Profile());
|
||||
return p;
|
||||
}
|
||||
|
||||
public static boolean hasProfile(String name) {
|
||||
if (name == null)
|
||||
name = DEFAULT_PROFILE;
|
||||
return getProfiles().containsKey(name);
|
||||
}
|
||||
|
||||
public static Map<String, Profile> getProfiles() {
|
||||
return SETTINGS.getConfigurations();
|
||||
}
|
||||
|
||||
public static Collection<Profile> getProfilesFiltered() {
|
||||
return CollectionUtils.filter(getProfiles().values(), t -> t != null && t.getName() != null);
|
||||
}
|
||||
|
||||
public static boolean putProfile(Profile ver) {
|
||||
if (ver == null || ver.getName() == null || getProfiles().containsKey(ver.getName()))
|
||||
return false;
|
||||
getProfiles().put(ver.getName(), ver);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static boolean delProfile(Profile ver) {
|
||||
return delProfile(ver.getName());
|
||||
}
|
||||
|
||||
public static boolean delProfile(String ver) {
|
||||
if (DEFAULT_PROFILE.equals(ver)) {
|
||||
MessageBox.show(C.i18n("settings.cannot_remove_default_config"));
|
||||
return false;
|
||||
}
|
||||
boolean notify = false;
|
||||
if (getLastProfile().getName().equals(ver))
|
||||
notify = true;
|
||||
boolean flag = getProfiles().remove(ver) != null;
|
||||
if (notify && flag)
|
||||
onProfileChanged();
|
||||
return flag;
|
||||
}
|
||||
|
||||
static void onProfileChanged() {
|
||||
Profile p = getLastProfile();
|
||||
if (p == null)
|
||||
throw new Error("No profiles here, it should not happen");
|
||||
HMCLApi.EVENT_BUS.fireChannel(new ProfileChangedEvent(SETTINGS, p));
|
||||
p.onSelected();
|
||||
}
|
||||
|
||||
public static void onProfileLoading() {
|
||||
HMCLApi.EVENT_BUS.fireChannel(new ProfileLoadingEvent(SETTINGS));
|
||||
onProfileChanged();
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
@JsonAdapter(Theme.TypeAdapter.class)
|
||||
public class Theme {
|
||||
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
|
||||
|
||||
public static final Color[] SUGGESTED_COLORS = new Color[]{
|
||||
Color.web("#5C6BC0"), // blue
|
||||
Color.web("#283593"), // dark blue
|
||||
Color.web("#43A047"), // green
|
||||
Color.web("#E67E22"), // orange
|
||||
Color.web("#9C27B0"), // purple
|
||||
Color.web("#B71C1C") // red
|
||||
};
|
||||
|
||||
private final String color;
|
||||
private final String name;
|
||||
|
||||
Theme(String name, String color) {
|
||||
this.name = name;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public boolean isCustom() {
|
||||
return name.startsWith("#");
|
||||
}
|
||||
|
||||
public boolean isLight() {
|
||||
return Color.web(color).grayscale().getRed() >= 0.5;
|
||||
}
|
||||
|
||||
public Color getForegroundColor() {
|
||||
return isLight() ? Color.BLACK : Color.WHITE;
|
||||
}
|
||||
|
||||
public String[] getStylesheets() {
|
||||
String css;
|
||||
try {
|
||||
File temp = File.createTempFile("hmcl", ".css");
|
||||
FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
|
||||
.replace("%base-color%", color)
|
||||
.replace("%font-color%", getColorDisplayName(getForegroundColor())));
|
||||
css = temp.toURI().toString();
|
||||
} catch (IOException | NullPointerException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet. Fallback to blue theme.", e);
|
||||
css = "/assets/css/blue.css";
|
||||
}
|
||||
|
||||
return new String[]{
|
||||
css,
|
||||
"/assets/css/root.css"
|
||||
};
|
||||
}
|
||||
|
||||
public static Theme custom(String color) {
|
||||
if (!color.startsWith("#"))
|
||||
throw new IllegalArgumentException();
|
||||
return new Theme(color, color);
|
||||
}
|
||||
|
||||
public static Optional<Theme> getTheme(String name) {
|
||||
if (name == null)
|
||||
return Optional.empty();
|
||||
else if (name.equalsIgnoreCase("blue"))
|
||||
return Optional.of(custom("#5C6BC0"));
|
||||
else if (name.equalsIgnoreCase("darker_blue"))
|
||||
return Optional.of(custom("#283593"));
|
||||
else if (name.equalsIgnoreCase("green"))
|
||||
return Optional.of(custom("#43A047"));
|
||||
else if (name.equalsIgnoreCase("orange"))
|
||||
return Optional.of(custom("#E67E22"));
|
||||
else if (name.equalsIgnoreCase("purple"))
|
||||
return Optional.of(custom("#9C27B0"));
|
||||
else if (name.equalsIgnoreCase("red"))
|
||||
return Optional.of(custom("#F44336"));
|
||||
|
||||
if (name.startsWith("#"))
|
||||
try {
|
||||
Color.web(name);
|
||||
return Optional.of(custom(name));
|
||||
} catch (IllegalArgumentException ignore) {
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
public static String getColorDisplayName(Color c) {
|
||||
return c != null ? String.format("#%02x%02x%02x", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)).toUpperCase() : null;
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> foregroundFillBinding() {
|
||||
return BindingMapping.of(config().themeProperty())
|
||||
.map(Theme::getForegroundColor);
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> blackFillBinding() {
|
||||
return Bindings.createObjectBinding(() -> Color.BLACK);
|
||||
}
|
||||
|
||||
public static ObjectBinding<Color> whiteFillBinding() {
|
||||
return Bindings.createObjectBinding(() -> Color.WHITE);
|
||||
}
|
||||
|
||||
public static class TypeAdapter extends com.google.gson.TypeAdapter<Theme> {
|
||||
@Override
|
||||
public void write(JsonWriter out, Theme value) throws IOException {
|
||||
out.value(value.getName().toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Theme read(JsonReader in) throws IOException {
|
||||
return getTheme(in.nextString()).orElse(Theme.BLUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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
|
||||
@@ -13,634 +13,329 @@
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.setting;
|
||||
|
||||
import com.google.gson.*;
|
||||
import com.google.gson.annotations.JsonAdapter;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Type;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.util.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.api.game.LaunchOptions;
|
||||
import org.jackhuang.hmcl.api.game.GameDirType;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.api.event.EventHandler;
|
||||
import org.jackhuang.hmcl.api.event.PropertyChangedEvent;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.util.sys.Java;
|
||||
import org.jackhuang.hmcl.util.sys.OS;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
@JsonAdapter(VersionSetting.Serializer.class)
|
||||
public final class VersionSetting {
|
||||
public class VersionSetting {
|
||||
|
||||
public transient String id;
|
||||
|
||||
private boolean global = false;
|
||||
|
||||
public boolean isGlobal() {
|
||||
return global;
|
||||
}
|
||||
|
||||
public void setGlobal(boolean global) {
|
||||
this.global = global;
|
||||
}
|
||||
|
||||
private final BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", false);
|
||||
|
||||
public BooleanProperty usesGlobalProperty() {
|
||||
return usesGlobalProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* HMCL Version Settings have been divided into 2 parts.
|
||||
* 1. Global settings.
|
||||
* 2. Version settings.
|
||||
* If a version claims that it uses global settings, its version setting will be disabled.
|
||||
*
|
||||
* Defaults false because if one version uses global first, custom version file will not be generated.
|
||||
*/
|
||||
public boolean isUsesGlobal() {
|
||||
return usesGlobalProperty.get();
|
||||
}
|
||||
|
||||
public void setUsesGlobal(boolean usesGlobal) {
|
||||
usesGlobalProperty.set(usesGlobal);
|
||||
}
|
||||
|
||||
// java
|
||||
|
||||
private final StringProperty javaProperty = new SimpleStringProperty(this, "java", "");
|
||||
|
||||
public StringProperty javaProperty() {
|
||||
return javaProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Java version or "Custom" if user customizes java directory, "Default" if the jvm that this app relies on.
|
||||
*/
|
||||
public String getJava() {
|
||||
return javaProperty.get();
|
||||
}
|
||||
|
||||
public void setJava(String java) {
|
||||
javaProperty.set(java);
|
||||
}
|
||||
|
||||
public boolean isUsesCustomJavaDir() {
|
||||
return "Custom".equals(getJava());
|
||||
}
|
||||
|
||||
public void setUsesCustomJavaDir() {
|
||||
setJava("Custom");
|
||||
setDefaultJavaPath(null);
|
||||
}
|
||||
|
||||
private final StringProperty defaultJavaPathProperty = new SimpleStringProperty(this, "defaultJavaPath", "");
|
||||
|
||||
/**
|
||||
* Path to Java executable, or null if user customizes java directory.
|
||||
* It's used to determine which JRE to use when multiple JREs match the selected Java version.
|
||||
*/
|
||||
public String getDefaultJavaPath() {
|
||||
return defaultJavaPathProperty.get();
|
||||
}
|
||||
|
||||
public void setDefaultJavaPath(String defaultJavaPath) {
|
||||
defaultJavaPathProperty.set(defaultJavaPath);
|
||||
}
|
||||
|
||||
private final StringProperty javaDirProperty = new SimpleStringProperty(this, "javaDir", "");
|
||||
|
||||
public StringProperty javaDirProperty() {
|
||||
return javaDirProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* User customized java directory or null if user uses system Java.
|
||||
*/
|
||||
public String getJavaDir() {
|
||||
return javaDirProperty.get();
|
||||
}
|
||||
|
||||
public void setJavaDir(String javaDir) {
|
||||
javaDirProperty.set(javaDir);
|
||||
}
|
||||
|
||||
private final StringProperty wrapperProperty = new SimpleStringProperty(this, "wrapper", "");
|
||||
|
||||
public StringProperty wrapperProperty() {
|
||||
return wrapperProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The command to launch java, i.e. optirun.
|
||||
*/
|
||||
public String getWrapper() {
|
||||
return wrapperProperty.get();
|
||||
}
|
||||
|
||||
public void setWrapper(String wrapper) {
|
||||
wrapperProperty.set(wrapper);
|
||||
}
|
||||
|
||||
private final StringProperty permSizeProperty = new SimpleStringProperty(this, "permSize", "");
|
||||
|
||||
public StringProperty permSizeProperty() {
|
||||
return permSizeProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The permanent generation size of JVM garbage collection.
|
||||
*/
|
||||
public String getPermSize() {
|
||||
return permSizeProperty.get();
|
||||
}
|
||||
|
||||
public void setPermSize(String permSize) {
|
||||
permSizeProperty.set(permSize);
|
||||
}
|
||||
|
||||
private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY);
|
||||
|
||||
public IntegerProperty maxMemoryProperty() {
|
||||
return maxMemoryProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum memory/MB that JVM can allocate for heap.
|
||||
*/
|
||||
public int getMaxMemory() {
|
||||
return maxMemoryProperty.get();
|
||||
}
|
||||
|
||||
public void setMaxMemory(int maxMemory) {
|
||||
maxMemoryProperty.set(maxMemory);
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum memory that JVM can allocate for heap.
|
||||
*/
|
||||
private final ObjectProperty<Integer> minMemoryProperty = new SimpleObjectProperty<>(this, "minMemory", null);
|
||||
|
||||
public ObjectProperty<Integer> minMemoryProperty() {
|
||||
return minMemoryProperty;
|
||||
}
|
||||
|
||||
public Integer getMinMemory() {
|
||||
return minMemoryProperty.get();
|
||||
}
|
||||
|
||||
public void setMinMemory(Integer minMemory) {
|
||||
minMemoryProperty.set(minMemory);
|
||||
}
|
||||
|
||||
private final StringProperty preLaunchCommandProperty = new SimpleStringProperty(this, "precalledCommand", "");
|
||||
|
||||
public StringProperty preLaunchCommandProperty() {
|
||||
return preLaunchCommandProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The command that will be executed before launching the Minecraft.
|
||||
* Operating system relevant.
|
||||
*/
|
||||
public String getPreLaunchCommand() {
|
||||
return preLaunchCommandProperty.get();
|
||||
}
|
||||
|
||||
public void setPreLaunchCommand(String preLaunchCommand) {
|
||||
preLaunchCommandProperty.set(preLaunchCommand);
|
||||
}
|
||||
|
||||
// options
|
||||
|
||||
private final StringProperty javaArgsProperty = new SimpleStringProperty(this, "javaArgs", "");
|
||||
|
||||
public StringProperty javaArgsProperty() {
|
||||
return javaArgsProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user customized arguments passed to JVM.
|
||||
*/
|
||||
public String getJavaArgs() {
|
||||
return javaArgsProperty.get();
|
||||
}
|
||||
|
||||
public void setJavaArgs(String javaArgs) {
|
||||
javaArgsProperty.set(javaArgs);
|
||||
}
|
||||
|
||||
private final StringProperty minecraftArgsProperty = new SimpleStringProperty(this, "minecraftArgs", "");
|
||||
|
||||
public StringProperty minecraftArgsProperty() {
|
||||
return minecraftArgsProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The user customized arguments passed to Minecraft.
|
||||
*/
|
||||
public String getMinecraftArgs() {
|
||||
return minecraftArgsProperty.get();
|
||||
}
|
||||
|
||||
public void setMinecraftArgs(String minecraftArgs) {
|
||||
minecraftArgsProperty.set(minecraftArgs);
|
||||
}
|
||||
|
||||
private final BooleanProperty noJVMArgsProperty = new SimpleBooleanProperty(this, "noJVMArgs", false);
|
||||
|
||||
public BooleanProperty noJVMArgsProperty() {
|
||||
return noJVMArgsProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if disallow HMCL use default JVM arguments.
|
||||
*/
|
||||
public boolean isNoJVMArgs() {
|
||||
return noJVMArgsProperty.get();
|
||||
}
|
||||
|
||||
public void setNoJVMArgs(boolean noJVMArgs) {
|
||||
noJVMArgsProperty.set(noJVMArgs);
|
||||
}
|
||||
|
||||
private final BooleanProperty notCheckJVMProperty = new SimpleBooleanProperty(this, "notCheckJVM", false);
|
||||
|
||||
public BooleanProperty notCheckJVMProperty() {
|
||||
return notCheckJVMProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if HMCL does not check JVM validity.
|
||||
*/
|
||||
public boolean isNotCheckJVM() {
|
||||
return notCheckJVMProperty.get();
|
||||
}
|
||||
|
||||
public void setNotCheckJVM(boolean notCheckJVM) {
|
||||
notCheckJVMProperty.set(notCheckJVM);
|
||||
}
|
||||
|
||||
private final BooleanProperty notCheckGameProperty = new SimpleBooleanProperty(this, "notCheckGame", false);
|
||||
|
||||
public BooleanProperty notCheckGameProperty() {
|
||||
return notCheckGameProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if HMCL does not check game's completeness.
|
||||
*/
|
||||
public boolean isNotCheckGame() {
|
||||
return notCheckGameProperty.get();
|
||||
}
|
||||
|
||||
public void setNotCheckGame(boolean notCheckGame) {
|
||||
notCheckGameProperty.set(notCheckGame);
|
||||
}
|
||||
|
||||
private final BooleanProperty showLogsProperty = new SimpleBooleanProperty(this, "showLogs", false);
|
||||
|
||||
public BooleanProperty showLogsProperty() {
|
||||
return showLogsProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if show the logs after game launched.
|
||||
*/
|
||||
public boolean isShowLogs() {
|
||||
return showLogsProperty.get();
|
||||
}
|
||||
|
||||
public void setShowLogs(boolean showLogs) {
|
||||
showLogsProperty.set(showLogs);
|
||||
}
|
||||
|
||||
// Minecraft settings.
|
||||
|
||||
private final StringProperty serverIpProperty = new SimpleStringProperty(this, "serverIp", "");
|
||||
|
||||
public StringProperty serverIpProperty() {
|
||||
return serverIpProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The server ip that will be entered after Minecraft successfully loaded ly.
|
||||
*
|
||||
* Format: ip:port or without port.
|
||||
*/
|
||||
public String getServerIp() {
|
||||
return serverIpProperty.get();
|
||||
}
|
||||
|
||||
public void setServerIp(String serverIp) {
|
||||
serverIpProperty.set(serverIp);
|
||||
}
|
||||
|
||||
|
||||
private final BooleanProperty fullscreenProperty = new SimpleBooleanProperty(this, "fullscreen", false);
|
||||
|
||||
public BooleanProperty fullscreenProperty() {
|
||||
return fullscreenProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if Minecraft started in fullscreen mode.
|
||||
*/
|
||||
public boolean isFullscreen() {
|
||||
return fullscreenProperty.get();
|
||||
}
|
||||
|
||||
public void setFullscreen(boolean fullscreen) {
|
||||
fullscreenProperty.set(fullscreen);
|
||||
}
|
||||
|
||||
private final IntegerProperty widthProperty = new SimpleIntegerProperty(this, "width", 854);
|
||||
|
||||
public IntegerProperty widthProperty() {
|
||||
return widthProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The width of Minecraft window, defaults 800.
|
||||
*
|
||||
* The field saves int value.
|
||||
* String type prevents unexpected value from JsonParseException.
|
||||
* We can only reset this field instead of recreating the whole setting file.
|
||||
*/
|
||||
public int getWidth() {
|
||||
return widthProperty.get();
|
||||
}
|
||||
|
||||
public void setWidth(int width) {
|
||||
widthProperty.set(width);
|
||||
}
|
||||
|
||||
|
||||
private final IntegerProperty heightProperty = new SimpleIntegerProperty(this, "height", 480);
|
||||
|
||||
public IntegerProperty heightProperty() {
|
||||
return heightProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* The height of Minecraft window, defaults 480.
|
||||
*
|
||||
* The field saves int value.
|
||||
* String type prevents unexpected value from JsonParseException.
|
||||
* We can only reset this field instead of recreating the whole setting file.
|
||||
*/
|
||||
public int getHeight() {
|
||||
return heightProperty.get();
|
||||
}
|
||||
|
||||
public void setHeight(int height) {
|
||||
heightProperty.set(height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 0 - .minecraft<br/>
|
||||
* 1 - .minecraft/versions/<version>/<br/>
|
||||
*/
|
||||
private final ObjectProperty<EnumGameDirectory> gameDirTypeProperty = new SimpleObjectProperty<>(this, "gameDirType", EnumGameDirectory.ROOT_FOLDER);
|
||||
|
||||
public ObjectProperty<EnumGameDirectory> gameDirTypeProperty() {
|
||||
return gameDirTypeProperty;
|
||||
}
|
||||
|
||||
public EnumGameDirectory getGameDirType() {
|
||||
return gameDirTypeProperty.get();
|
||||
}
|
||||
|
||||
public void setGameDirType(EnumGameDirectory gameDirType) {
|
||||
gameDirTypeProperty.set(gameDirType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Your custom gameDir
|
||||
*/
|
||||
private final StringProperty gameDirProperty = new SimpleStringProperty(this, "gameDir", "");
|
||||
|
||||
public StringProperty gameDirProperty() {
|
||||
return gameDirProperty;
|
||||
}
|
||||
|
||||
public String getGameDir() {
|
||||
return gameDirProperty.get();
|
||||
}
|
||||
|
||||
public void setGameDir(String gameDir) {
|
||||
gameDirProperty.set(gameDir);
|
||||
}
|
||||
|
||||
// launcher settings
|
||||
public transient boolean isGlobal = false;
|
||||
|
||||
@SerializedName("usesGlobal")
|
||||
private boolean usesGlobal;
|
||||
@SerializedName("javaArgs")
|
||||
private String javaArgs;
|
||||
@SerializedName("minecraftArgs")
|
||||
private String minecraftArgs;
|
||||
@SerializedName("maxMemory")
|
||||
private String maxMemory;
|
||||
@SerializedName("permSize")
|
||||
private String permSize;
|
||||
@SerializedName("width")
|
||||
private String width;
|
||||
@SerializedName("height")
|
||||
private String height;
|
||||
@SerializedName("javaDir")
|
||||
private String javaDir;
|
||||
@SerializedName("precalledCommand")
|
||||
private String precalledCommand;
|
||||
@SerializedName("serverIp")
|
||||
private String serverIp;
|
||||
@SerializedName("java")
|
||||
private String java;
|
||||
@SerializedName("wrapper")
|
||||
private String wrapper;
|
||||
@SerializedName("fullscreen")
|
||||
private boolean fullscreen;
|
||||
@SerializedName("noJVMArgs")
|
||||
private boolean noJVMArgs;
|
||||
@SerializedName("notCheckGame")
|
||||
private boolean notCheckGame;
|
||||
|
||||
/**
|
||||
* 0 - Close the launcher when the game starts.<br/>
|
||||
* 1 - Hide the launcher when the game starts.<br/>
|
||||
* 2 - Keep the launcher open.<br/>
|
||||
*/
|
||||
private final ObjectProperty<LauncherVisibility> launcherVisibilityProperty = new SimpleObjectProperty<>(this, "launcherVisibility", LauncherVisibility.HIDE);
|
||||
@SerializedName("launcherVisibility")
|
||||
private int launcherVisibility;
|
||||
|
||||
public ObjectProperty<LauncherVisibility> launcherVisibilityProperty() {
|
||||
return launcherVisibilityProperty;
|
||||
/**
|
||||
* 0 - .minecraft<br/>
|
||||
* 1 - .minecraft/versions/<version>/<br/>
|
||||
*/
|
||||
@SerializedName("gameDirType")
|
||||
private int gameDirType;
|
||||
|
||||
public transient final EventHandler<PropertyChangedEvent> propertyChanged = new EventHandler<>();
|
||||
|
||||
public VersionSetting() {
|
||||
fullscreen = usesGlobal = false;
|
||||
launcherVisibility = 1;
|
||||
gameDirType = 0;
|
||||
javaDir = java = minecraftArgs = serverIp = precalledCommand = wrapper = "";
|
||||
}
|
||||
|
||||
public String getJavaDir() {
|
||||
Java j = getJava();
|
||||
if (j.getHome() == null)
|
||||
return javaDir;
|
||||
else
|
||||
return j.getJava();
|
||||
}
|
||||
|
||||
public String getSettingsJavaDir() {
|
||||
return javaDir;
|
||||
}
|
||||
|
||||
public File getJavaDirFile() {
|
||||
return new File(getJavaDir());
|
||||
}
|
||||
|
||||
public void setJavaDir(String javaDir) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "javaDir", this.javaDir, javaDir);
|
||||
this.javaDir = javaDir;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public Java getJava() {
|
||||
return Java.JAVA.get(getJavaIndexInAllJavas());
|
||||
}
|
||||
|
||||
public int getJavaIndexInAllJavas() {
|
||||
if (StrUtils.isBlank(java) && StrUtils.isNotBlank(javaDir))
|
||||
java = "Custom";
|
||||
int idx = Java.JAVA.indexOf(new Java(java, null));
|
||||
if (idx == -1) {
|
||||
java = Java.suggestedJava().getName();
|
||||
idx = 0;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
public void setJava(Java java) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "java", this.java, java);
|
||||
if (java == null)
|
||||
this.java = Java.JAVA.get(0).getName();
|
||||
else {
|
||||
int idx = Java.JAVA.indexOf(java);
|
||||
if (idx == -1)
|
||||
return;
|
||||
this.java = java.getName();
|
||||
}
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public String getJavaArgs() {
|
||||
if (StrUtils.isBlank(javaArgs))
|
||||
return "";
|
||||
return javaArgs;
|
||||
}
|
||||
|
||||
public void setJavaArgs(String javaArgs) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "javaArgs", this.javaArgs, javaArgs);
|
||||
this.javaArgs = javaArgs;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public String getMaxMemory() {
|
||||
if (StrUtils.isBlank(maxMemory))
|
||||
return String.valueOf(OS.getSuggestedMemorySize());
|
||||
return maxMemory;
|
||||
}
|
||||
|
||||
public void setMaxMemory(String maxMemory) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "maxMemory", this.maxMemory, maxMemory);
|
||||
this.maxMemory = maxMemory;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public String getWidth() {
|
||||
if (StrUtils.isBlank(width))
|
||||
return "854";
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(String width) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "width", this.width, width);
|
||||
this.width = width;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public String getHeight() {
|
||||
if (StrUtils.isBlank(height))
|
||||
return "480";
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(String height) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "height", this.height, height);
|
||||
this.height = height;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public boolean isFullscreen() {
|
||||
return fullscreen;
|
||||
}
|
||||
|
||||
public void setFullscreen(boolean fullscreen) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "fullscreen", this.fullscreen, fullscreen);
|
||||
this.fullscreen = fullscreen;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public LauncherVisibility getLauncherVisibility() {
|
||||
return launcherVisibilityProperty.get();
|
||||
return LauncherVisibility.values()[launcherVisibility];
|
||||
}
|
||||
|
||||
public void setLauncherVisibility(LauncherVisibility launcherVisibility) {
|
||||
launcherVisibilityProperty.set(launcherVisibility);
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "launcherVisibility", this.launcherVisibility, launcherVisibility);
|
||||
this.launcherVisibility = launcherVisibility.ordinal();
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public JavaVersion getJavaVersion() throws InterruptedException {
|
||||
// TODO: lazy initialization may result in UI suspension.
|
||||
if (StringUtils.isBlank(getJava()))
|
||||
setJava(StringUtils.isBlank(getJavaDir()) ? "Default" : "Custom");
|
||||
if ("Default".equals(getJava())) return JavaVersion.fromCurrentEnvironment();
|
||||
else if (isUsesCustomJavaDir()) {
|
||||
try {
|
||||
return JavaVersion.fromExecutable(Paths.get(getJavaDir()));
|
||||
} catch (IOException e) {
|
||||
return null; // Custom Java Directory not found,
|
||||
}
|
||||
} else if (StringUtils.isNotBlank(getJava())) {
|
||||
List<JavaVersion> matchedJava = JavaVersion.getJavas().stream()
|
||||
.filter(java -> java.getVersion().equals(getJava()))
|
||||
.collect(Collectors.toList());
|
||||
if (matchedJava.isEmpty()) {
|
||||
setJava("Default");
|
||||
return JavaVersion.fromCurrentEnvironment();
|
||||
} else {
|
||||
return matchedJava.stream()
|
||||
.filter(java -> java.getBinary().toString().equals(getDefaultJavaPath()))
|
||||
.findFirst()
|
||||
.orElse(matchedJava.get(0));
|
||||
}
|
||||
} else throw new Error();
|
||||
public GameDirType getGameDirType() {
|
||||
if (gameDirType < 0 || gameDirType > 1)
|
||||
setGameDirType(GameDirType.ROOT_FOLDER);
|
||||
return GameDirType.values()[gameDirType];
|
||||
}
|
||||
|
||||
public void setJavaVersion(JavaVersion java) {
|
||||
setJava(java.getVersion());
|
||||
setDefaultJavaPath(java.getBinary().toString());
|
||||
public void setGameDirType(GameDirType gameDirType) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "gameDirType", this.gameDirType, gameDirType);
|
||||
this.gameDirType = gameDirType.ordinal();
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public void addPropertyChangedListener(InvalidationListener listener) {
|
||||
usesGlobalProperty.addListener(listener);
|
||||
javaProperty.addListener(listener);
|
||||
javaDirProperty.addListener(listener);
|
||||
wrapperProperty.addListener(listener);
|
||||
permSizeProperty.addListener(listener);
|
||||
maxMemoryProperty.addListener(listener);
|
||||
minMemoryProperty.addListener(listener);
|
||||
preLaunchCommandProperty.addListener(listener);
|
||||
javaArgsProperty.addListener(listener);
|
||||
minecraftArgsProperty.addListener(listener);
|
||||
noJVMArgsProperty.addListener(listener);
|
||||
notCheckGameProperty.addListener(listener);
|
||||
notCheckJVMProperty.addListener(listener);
|
||||
showLogsProperty.addListener(listener);
|
||||
serverIpProperty.addListener(listener);
|
||||
fullscreenProperty.addListener(listener);
|
||||
widthProperty.addListener(listener);
|
||||
heightProperty.addListener(listener);
|
||||
gameDirTypeProperty.addListener(listener);
|
||||
gameDirProperty.addListener(listener);
|
||||
launcherVisibilityProperty.addListener(listener);
|
||||
defaultJavaPathProperty.addListener(listener);
|
||||
public String getPermSize() {
|
||||
return permSize;
|
||||
}
|
||||
|
||||
public LaunchOptions toLaunchOptions(File gameDir) throws InterruptedException {
|
||||
JavaVersion javaVersion = Optional.ofNullable(getJavaVersion()).orElse(JavaVersion.fromCurrentEnvironment());
|
||||
LaunchOptions.Builder builder = new LaunchOptions.Builder()
|
||||
.setGameDir(gameDir)
|
||||
.setJava(javaVersion)
|
||||
.setVersionName(Metadata.TITLE)
|
||||
.setProfileName(Metadata.TITLE)
|
||||
.setMinecraftArgs(getMinecraftArgs())
|
||||
.setJavaArgs(getJavaArgs())
|
||||
.setMaxMemory(getMaxMemory())
|
||||
.setMinMemory(getMinMemory())
|
||||
.setMetaspace(Lang.toIntOrNull(getPermSize()))
|
||||
.setWidth(getWidth())
|
||||
.setHeight(getHeight())
|
||||
.setFullscreen(isFullscreen())
|
||||
.setServerIp(getServerIp())
|
||||
.setWrapper(getWrapper())
|
||||
.setPrecalledCommand(getPreLaunchCommand())
|
||||
.setNoGeneratedJVMArgs(isNoJVMArgs());
|
||||
if (config().hasProxy()) {
|
||||
builder.setProxyHost(config().getProxyHost());
|
||||
builder.setProxyPort(config().getProxyPort());
|
||||
if (config().hasProxyAuth()) {
|
||||
builder.setProxyUser(config().getProxyUser());
|
||||
builder.setProxyPass(config().getProxyPass());
|
||||
}
|
||||
}
|
||||
return builder.create();
|
||||
public void setPermSize(String permSize) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "permSize", this.permSize, permSize);
|
||||
this.permSize = permSize;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public static class Serializer implements JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
|
||||
@Override
|
||||
public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializationContext context) {
|
||||
if (src == null) return JsonNull.INSTANCE;
|
||||
JsonObject obj = new JsonObject();
|
||||
public boolean isNoJVMArgs() {
|
||||
return noJVMArgs;
|
||||
}
|
||||
|
||||
obj.addProperty("usesGlobal", src.isUsesGlobal());
|
||||
obj.addProperty("javaArgs", src.getJavaArgs());
|
||||
obj.addProperty("minecraftArgs", src.getMinecraftArgs());
|
||||
obj.addProperty("maxMemory", src.getMaxMemory() <= 0 ? OperatingSystem.SUGGESTED_MEMORY : src.getMaxMemory());
|
||||
obj.addProperty("minMemory", src.getMinMemory());
|
||||
obj.addProperty("permSize", src.getPermSize());
|
||||
obj.addProperty("width", src.getWidth());
|
||||
obj.addProperty("height", src.getHeight());
|
||||
obj.addProperty("javaDir", src.getJavaDir());
|
||||
obj.addProperty("precalledCommand", src.getPreLaunchCommand());
|
||||
obj.addProperty("serverIp", src.getServerIp());
|
||||
obj.addProperty("java", src.getJava());
|
||||
obj.addProperty("wrapper", src.getWrapper());
|
||||
obj.addProperty("fullscreen", src.isFullscreen());
|
||||
obj.addProperty("noJVMArgs", src.isNoJVMArgs());
|
||||
obj.addProperty("notCheckGame", src.isNotCheckGame());
|
||||
obj.addProperty("notCheckJVM", src.isNotCheckJVM());
|
||||
obj.addProperty("showLogs", src.isShowLogs());
|
||||
obj.addProperty("gameDir", src.getGameDir());
|
||||
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
|
||||
obj.addProperty("gameDirType", src.getGameDirType().ordinal());
|
||||
obj.addProperty("defaultJavaPath", src.getDefaultJavaPath());
|
||||
public void setNoJVMArgs(boolean noJVMArgs) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "noJVMArgs", this.noJVMArgs, noJVMArgs);
|
||||
this.noJVMArgs = noJVMArgs;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||
if (json == null || json == JsonNull.INSTANCE || !(json instanceof JsonObject))
|
||||
return null;
|
||||
JsonObject obj = (JsonObject) json;
|
||||
|
||||
int maxMemoryN = parseJsonPrimitive(Optional.ofNullable(obj.get("maxMemory")).map(JsonElement::getAsJsonPrimitive).orElse(null), OperatingSystem.SUGGESTED_MEMORY);
|
||||
if (maxMemoryN <= 0) maxMemoryN = OperatingSystem.SUGGESTED_MEMORY;
|
||||
|
||||
VersionSetting vs = new VersionSetting();
|
||||
|
||||
vs.setUsesGlobal(Optional.ofNullable(obj.get("usesGlobal")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setJavaArgs(Optional.ofNullable(obj.get("javaArgs")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setMinecraftArgs(Optional.ofNullable(obj.get("minecraftArgs")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setMaxMemory(maxMemoryN);
|
||||
vs.setMinMemory(Optional.ofNullable(obj.get("minMemory")).map(JsonElement::getAsInt).orElse(null));
|
||||
vs.setPermSize(Optional.ofNullable(obj.get("permSize")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setWidth(Optional.ofNullable(obj.get("width")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0));
|
||||
vs.setHeight(Optional.ofNullable(obj.get("height")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0));
|
||||
vs.setJavaDir(Optional.ofNullable(obj.get("javaDir")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setPreLaunchCommand(Optional.ofNullable(obj.get("precalledCommand")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setServerIp(Optional.ofNullable(obj.get("serverIp")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setJava(Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setWrapper(Optional.ofNullable(obj.get("wrapper")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setGameDir(Optional.ofNullable(obj.get("gameDir")).map(JsonElement::getAsString).orElse(""));
|
||||
vs.setFullscreen(Optional.ofNullable(obj.get("fullscreen")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNoJVMArgs(Optional.ofNullable(obj.get("noJVMArgs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckGame(Optional.ofNullable(obj.get("notCheckGame")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setNotCheckJVM(Optional.ofNullable(obj.get("notCheckJVM")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
|
||||
vs.setLauncherVisibility(LauncherVisibility.values()[Optional.ofNullable(obj.get("launcherVisibility")).map(JsonElement::getAsInt).orElse(1)]);
|
||||
vs.setGameDirType(EnumGameDirectory.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(0)]);
|
||||
vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null));
|
||||
|
||||
return vs;
|
||||
}
|
||||
|
||||
private int parseJsonPrimitive(JsonPrimitive primitive) {
|
||||
return parseJsonPrimitive(primitive, 0);
|
||||
}
|
||||
|
||||
private int parseJsonPrimitive(JsonPrimitive primitive, int defaultValue) {
|
||||
if (primitive == null)
|
||||
return defaultValue;
|
||||
else if (primitive.isNumber())
|
||||
return primitive.getAsInt();
|
||||
else
|
||||
return Lang.parseInt(primitive.getAsString(), defaultValue);
|
||||
public String getMinecraftArgs() {
|
||||
return minecraftArgs;
|
||||
}
|
||||
|
||||
public void setMinecraftArgs(String minecraftArgs) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "minecraftArgs", this.minecraftArgs, minecraftArgs);
|
||||
this.minecraftArgs = minecraftArgs;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public String getPrecalledCommand() {
|
||||
return precalledCommand;
|
||||
}
|
||||
|
||||
public void setPrecalledCommand(String precalledCommand) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "precalledCommand", this.precalledCommand, precalledCommand);
|
||||
this.precalledCommand = precalledCommand;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public String getWrapper() {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public void setWrapper(String wrapper) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "wrapper", this.wrapper, wrapper);
|
||||
this.wrapper = wrapper;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public String getServerIp() {
|
||||
return serverIp;
|
||||
}
|
||||
|
||||
public void setServerIp(String serverIp) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "serverIp", this.serverIp, serverIp);
|
||||
this.serverIp = serverIp;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public boolean isNotCheckGame() {
|
||||
return notCheckGame;
|
||||
}
|
||||
|
||||
public void setNotCheckGame(boolean notCheckGame) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "notCheckGame", this.notCheckGame, notCheckGame);
|
||||
this.notCheckGame = notCheckGame;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public boolean isUsesGlobal() {
|
||||
return usesGlobal;
|
||||
}
|
||||
|
||||
public void setUsesGlobal(boolean usesGlobal) {
|
||||
PropertyChangedEvent event = new PropertyChangedEvent(this, "usesGlobal", this.usesGlobal, usesGlobal);
|
||||
this.usesGlobal = usesGlobal;
|
||||
propertyChanged.fire(event);
|
||||
}
|
||||
|
||||
public LaunchOptions createLaunchOptions(File gameDir) {
|
||||
LaunchOptions x = new LaunchOptions();
|
||||
x.setFullscreen(isFullscreen());
|
||||
x.setWrapper(getWrapper());
|
||||
x.setGameDir(gameDir);
|
||||
x.setHeight(getHeight());
|
||||
x.setJavaArgs(getJavaArgs());
|
||||
x.setLaunchVersion(id);
|
||||
x.setMaxMemory(getMaxMemory());
|
||||
x.setMinecraftArgs(getMinecraftArgs());
|
||||
x.setName(Main.shortTitle());
|
||||
x.setType(Main.shortTitle());
|
||||
x.setVersionName(Main.shortTitle());
|
||||
x.setNoJVMArgs(isNoJVMArgs());
|
||||
x.setNotCheckGame(isNotCheckGame());
|
||||
x.setPermSize(getPermSize());
|
||||
x.setPrecalledCommand(getPrecalledCommand());
|
||||
x.setProxyHost(Settings.getInstance().getProxyHost());
|
||||
x.setProxyPort(Settings.getInstance().getProxyPort());
|
||||
x.setProxyUser(Settings.getInstance().getProxyUserName());
|
||||
x.setProxyPass(Settings.getInstance().getProxyPassword());
|
||||
x.setServerIp(getServerIp());
|
||||
x.setWidth(getWidth());
|
||||
|
||||
String str = getJavaDir();
|
||||
if (!getJavaDirFile().exists()) {
|
||||
HMCLog.err(C.i18n("launch.wrong_javadir"));
|
||||
setJava(null);
|
||||
str = getJavaDir();
|
||||
}
|
||||
x.setJavaDir(str);
|
||||
return x;
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Launcher;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.Version;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountList;
|
||||
import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
|
||||
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileList;
|
||||
import org.jackhuang.hmcl.ui.versions.GameItem;
|
||||
import org.jackhuang.hmcl.ui.versions.GameList;
|
||||
import org.jackhuang.hmcl.ui.versions.VersionPage;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
import org.jackhuang.hmcl.util.platform.JavaVersion;
|
||||
import org.jackhuang.hmcl.util.versioning.VersionNumber;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class Controllers {
|
||||
|
||||
private static Scene scene;
|
||||
private static Stage stage;
|
||||
private static MainPage mainPage = null;
|
||||
private static SettingsPage settingsPage = null;
|
||||
private static VersionPage versionPage = null;
|
||||
private static GameList gameListPage = null;
|
||||
private static AccountList accountListPage = null;
|
||||
private static ProfileList profileListPage = null;
|
||||
private static AuthlibInjectorServersPage serversPage = null;
|
||||
private static LeftPaneController leftPaneController;
|
||||
private static DecoratorController decorator;
|
||||
|
||||
public static Scene getScene() {
|
||||
return scene;
|
||||
}
|
||||
|
||||
public static Stage getStage() {
|
||||
return stage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static SettingsPage getSettingsPage() {
|
||||
if (settingsPage == null)
|
||||
settingsPage = new SettingsPage();
|
||||
return settingsPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static GameList getGameListPage() {
|
||||
if (gameListPage == null) {
|
||||
gameListPage = new GameList();
|
||||
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
});
|
||||
}
|
||||
return gameListPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static AccountList getAccountListPage() {
|
||||
if (accountListPage == null) {
|
||||
AccountList accountListPage = new AccountList();
|
||||
accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty());
|
||||
accountListPage.accountsProperty().bindContent(Accounts.accountsProperty());
|
||||
Controllers.accountListPage = accountListPage;
|
||||
}
|
||||
return accountListPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static ProfileList getProfileListPage() {
|
||||
if (profileListPage == null) {
|
||||
ProfileList profileListPage = new ProfileList();
|
||||
profileListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
|
||||
profileListPage.profilesProperty().bindContent(Profiles.profilesProperty());
|
||||
Controllers.profileListPage = profileListPage;
|
||||
}
|
||||
return profileListPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static VersionPage getVersionPage() {
|
||||
if (versionPage == null)
|
||||
versionPage = new VersionPage();
|
||||
return versionPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static AuthlibInjectorServersPage getServersPage() {
|
||||
if (serversPage == null)
|
||||
serversPage = new AuthlibInjectorServersPage();
|
||||
return serversPage;
|
||||
}
|
||||
|
||||
// FXThread
|
||||
public static DecoratorController getDecorator() {
|
||||
return decorator;
|
||||
}
|
||||
|
||||
public static MainPage getMainPage() {
|
||||
if (mainPage == null) {
|
||||
MainPage mainPage = new MainPage();
|
||||
FXUtils.applyDragListener(mainPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
|
||||
File modpack = modpacks.get(0);
|
||||
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
|
||||
});
|
||||
|
||||
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
|
||||
if (version != null) {
|
||||
mainPage.setCurrentGame(version);
|
||||
} else {
|
||||
mainPage.setCurrentGame(i18n("version.empty"));
|
||||
}
|
||||
});
|
||||
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
|
||||
mainPage.latestVersionProperty().bind(
|
||||
BindingMapping.of(UpdateChecker.latestVersionProperty())
|
||||
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
|
||||
|
||||
Profiles.registerVersionsListener(profile -> {
|
||||
HMCLGameRepository repository = profile.getRepository();
|
||||
List<Node> children = repository.getVersions().parallelStream()
|
||||
.filter(version -> !version.isHidden())
|
||||
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
|
||||
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
|
||||
.map(version -> {
|
||||
Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId()));
|
||||
node.setOnMouseClicked(e -> profile.setSelectedVersion(version.getId()));
|
||||
return node;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (profile == Profiles.getSelectedProfile())
|
||||
mainPage.getVersions().setAll(children);
|
||||
});
|
||||
});
|
||||
Controllers.mainPage = mainPage;
|
||||
}
|
||||
return mainPage;
|
||||
}
|
||||
|
||||
public static LeftPaneController getLeftPaneController() {
|
||||
return leftPaneController;
|
||||
}
|
||||
|
||||
public static void initialize(Stage stage) {
|
||||
Logging.LOG.info("Start initializing application");
|
||||
|
||||
Controllers.stage = stage;
|
||||
|
||||
stage.setOnCloseRequest(e -> Launcher.stopApplication());
|
||||
|
||||
decorator = new DecoratorController(stage, getMainPage());
|
||||
leftPaneController = new LeftPaneController();
|
||||
decorator.getDecorator().drawerProperty().setAll(leftPaneController);
|
||||
|
||||
if (config().getCommonDirType() == EnumCommonDirectory.CUSTOM &&
|
||||
!FileUtils.canCreateDirectory(config().getCommonDirectory())) {
|
||||
config().setCommonDirType(EnumCommonDirectory.DEFAULT);
|
||||
dialog(i18n("launcher.cache_directory.invalid"));
|
||||
}
|
||||
|
||||
Task.runAsync(JavaVersion::initialize).start();
|
||||
|
||||
scene = new Scene(decorator.getDecorator(), 800, 519);
|
||||
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
|
||||
|
||||
stage.getIcons().add(new Image("/assets/img/icon.png"));
|
||||
stage.setTitle(Metadata.TITLE);
|
||||
}
|
||||
|
||||
public static void dialog(Region content) {
|
||||
if (decorator != null)
|
||||
decorator.showDialog(content);
|
||||
}
|
||||
|
||||
public static void dialog(String text) {
|
||||
dialog(text, null);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title) {
|
||||
dialog(text, title, MessageType.INFORMATION);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, MessageType type) {
|
||||
dialog(text, title, type, null);
|
||||
}
|
||||
|
||||
public static void dialog(String text, String title, MessageType type, Runnable 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, onAccept, onCancel));
|
||||
}
|
||||
|
||||
public static InputDialogPane inputDialog(String text, FutureCallback<String> onResult) {
|
||||
InputDialogPane pane = new InputDialogPane(text, onResult);
|
||||
dialog(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static Region taskDialog(TaskExecutor executor, String title) {
|
||||
return taskDialog(executor, title, "");
|
||||
}
|
||||
|
||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
|
||||
return taskDialog(executor, title, subtitle, null);
|
||||
}
|
||||
|
||||
public static Region taskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
|
||||
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
|
||||
pane.setTitle(title);
|
||||
pane.setSubtitle(subtitle);
|
||||
pane.setExecutor(executor);
|
||||
dialog(pane);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static void navigate(Node node) {
|
||||
decorator.getNavigator().navigate(node);
|
||||
}
|
||||
|
||||
public static boolean isStopped() {
|
||||
return decorator == null;
|
||||
}
|
||||
|
||||
public static void shutdown() {
|
||||
mainPage = null;
|
||||
settingsPage = null;
|
||||
versionPage = null;
|
||||
serversPage = null;
|
||||
decorator = null;
|
||||
stage = null;
|
||||
scene = null;
|
||||
gameListPage = null;
|
||||
accountListPage = null;
|
||||
profileListPage = null;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class CrashWindow extends Stage {
|
||||
|
||||
public CrashWindow(String text) {
|
||||
Label lblCrash = new Label();
|
||||
if (UpdateChecker.isOutdated())
|
||||
lblCrash.setText(i18n("launcher.crash_out_dated"));
|
||||
else
|
||||
lblCrash.setText(i18n("launcher.crash"));
|
||||
lblCrash.setWrapText(true);
|
||||
|
||||
TextArea textArea = new TextArea();
|
||||
textArea.setText(text);
|
||||
textArea.setEditable(false);
|
||||
|
||||
Button btnContact = new Button();
|
||||
btnContact.setText(i18n("launcher.contact"));
|
||||
btnContact.setOnMouseClicked(event -> FXUtils.openLink(Metadata.CONTACT_URL));
|
||||
HBox box = new HBox();
|
||||
box.setStyle("-fx-padding: 8px;");
|
||||
box.getChildren().add(btnContact);
|
||||
box.setAlignment(Pos.CENTER_RIGHT);
|
||||
|
||||
BorderPane pane = new BorderPane();
|
||||
StackPane stackPane = new StackPane();
|
||||
stackPane.setStyle("-fx-padding: 8px;");
|
||||
stackPane.getChildren().add(lblCrash);
|
||||
pane.setTop(stackPane);
|
||||
pane.setCenter(textArea);
|
||||
pane.setBottom(box);
|
||||
|
||||
Scene scene = new Scene(pane, 800, 480);
|
||||
setScene(scene);
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
setTitle(i18n("message.error"));
|
||||
|
||||
setOnCloseRequest(e -> System.exit(1));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthInfo;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.ui.account.AccountLoginPane;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class DialogController {
|
||||
|
||||
public static AuthInfo logIn(Account account) throws CancellationException, AuthenticationException, InterruptedException {
|
||||
if (account instanceof YggdrasilAccount) {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
|
||||
JFXUtilities.runInFX(() -> {
|
||||
AccountLoginPane pane = new AccountLoginPane(account, it -> {
|
||||
res.set(it);
|
||||
latch.countDown();
|
||||
}, latch::countDown);
|
||||
Controllers.dialog(pane);
|
||||
});
|
||||
latch.await();
|
||||
return Optional.ofNullable(res.get()).orElseThrow(CancellationException::new);
|
||||
}
|
||||
return account.logIn();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.MouseMotionListener;
|
||||
import javax.swing.JFrame;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class DraggableFrame extends JFrame
|
||||
implements MouseListener, MouseMotionListener {
|
||||
|
||||
private int dragGripX;
|
||||
private int dragGripY;
|
||||
|
||||
public DraggableFrame() {
|
||||
addMouseListener(this);
|
||||
addMouseMotionListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.getButton() == 1 && isUndecorated()) {
|
||||
this.dragGripX = e.getX();
|
||||
this.dragGripY = e.getY();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
if ((e.getModifiersEx() & 0x400) != 0 && isUndecorated())
|
||||
setLocation(e.getXOnScreen() - this.dragGripX, e.getYOnScreen() - this.dragGripY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
}
|
||||
}
|
||||
@@ -1,539 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.property.Property;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.*;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.input.ScrollEvent;
|
||||
import javafx.scene.input.TransferMode;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Callback;
|
||||
import javafx.util.Duration;
|
||||
import javafx.util.StringConverter;
|
||||
import org.jackhuang.hmcl.util.*;
|
||||
import org.jackhuang.hmcl.util.i18n.I18n;
|
||||
import org.jackhuang.hmcl.util.io.FileUtils;
|
||||
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
import org.jackhuang.hmcl.util.platform.OperatingSystem;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Lang.tryCast;
|
||||
|
||||
public final class FXUtils {
|
||||
private FXUtils() {
|
||||
}
|
||||
|
||||
public static void checkFxUserThread() {
|
||||
if (!Platform.isFxApplicationThread()) {
|
||||
throw new IllegalStateException("Not on FX application thread; currentThread = "
|
||||
+ Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static InvalidationListener onInvalidating(Runnable action) {
|
||||
return arg -> action.run();
|
||||
}
|
||||
|
||||
public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
value.addListener((a, b, c) -> consumer.accept(c));
|
||||
}
|
||||
|
||||
public static <T> void onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
value.addListener(new WeakChangeListener<>((a, b, c) -> consumer.accept(c)));
|
||||
}
|
||||
|
||||
public static <T> void onChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
consumer.accept(value.getValue());
|
||||
onChange(value, consumer);
|
||||
}
|
||||
|
||||
public static <T> void onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
|
||||
consumer.accept(value.getValue());
|
||||
onWeakChange(value, consumer);
|
||||
}
|
||||
|
||||
public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {
|
||||
if (condition.getAsBoolean()) Platform.runLater(() -> runLaterIf(condition, runnable));
|
||||
else runnable.run();
|
||||
}
|
||||
|
||||
public static void limitSize(ImageView imageView, double maxWidth, double maxHeight) {
|
||||
imageView.setPreserveRatio(true);
|
||||
onChangeAndOperate(imageView.imageProperty(), image -> {
|
||||
if (image != null && (image.getWidth() > maxWidth || image.getHeight() > maxHeight)) {
|
||||
imageView.setFitHeight(maxHeight);
|
||||
imageView.setFitWidth(maxWidth);
|
||||
} else {
|
||||
imageView.setFitHeight(-1);
|
||||
imageView.setFitWidth(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ListenerPair<T> {
|
||||
private final ObservableValue<T> value;
|
||||
private final ChangeListener<? super T> listener;
|
||||
|
||||
ListenerPair(ObservableValue<T> value, ChangeListener<? super T> listener) {
|
||||
this.value = value;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
void bind() {
|
||||
value.addListener(listener);
|
||||
}
|
||||
|
||||
void unbind() {
|
||||
value.removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> void addListener(Node node, String key, ObservableValue<T> value, Consumer<? super T> callback) {
|
||||
ListenerPair<T> pair = new ListenerPair<>(value, (a, b, newValue) -> callback.accept(newValue));
|
||||
node.getProperties().put(key, pair);
|
||||
pair.bind();
|
||||
}
|
||||
|
||||
public static void removeListener(Node node, String key) {
|
||||
tryCast(node.getProperties().get(key), ListenerPair.class)
|
||||
.ifPresent(info -> {
|
||||
info.unbind();
|
||||
node.getProperties().remove(key);
|
||||
});
|
||||
}
|
||||
|
||||
public static void setValidateWhileTextChanged(Node field, boolean validate) {
|
||||
if (field instanceof JFXTextField) {
|
||||
if (validate) {
|
||||
addListener(field, "FXUtils.validation", ((JFXTextField) field).textProperty(), o -> ((JFXTextField) field).validate());
|
||||
} else {
|
||||
removeListener(field, "FXUtils.validation");
|
||||
}
|
||||
((JFXTextField) field).validate();
|
||||
} else if (field instanceof JFXPasswordField) {
|
||||
if (validate) {
|
||||
addListener(field, "FXUtils.validation", ((JFXPasswordField) field).textProperty(), o -> ((JFXPasswordField) field).validate());
|
||||
} else {
|
||||
removeListener(field, "FXUtils.validation");
|
||||
}
|
||||
((JFXPasswordField) field).validate();
|
||||
} else
|
||||
throw new IllegalArgumentException("Only JFXTextField and JFXPasswordField allowed");
|
||||
}
|
||||
|
||||
public static boolean getValidateWhileTextChanged(Node field) {
|
||||
return field.getProperties().containsKey("FXUtils.validation");
|
||||
}
|
||||
|
||||
public static void setOverflowHidden(Region region, boolean hidden) {
|
||||
if (hidden) {
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.widthProperty().bind(region.widthProperty());
|
||||
rectangle.heightProperty().bind(region.heightProperty());
|
||||
region.setClip(rectangle);
|
||||
} else {
|
||||
region.setClip(null);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean getOverflowHidden(Region region) {
|
||||
return region.getClip() != null;
|
||||
}
|
||||
|
||||
public static void setLimitWidth(Region region, double width) {
|
||||
region.setMaxWidth(width);
|
||||
region.setMinWidth(width);
|
||||
region.setPrefWidth(width);
|
||||
}
|
||||
|
||||
public static double getLimitWidth(Region region) {
|
||||
return region.getMaxWidth();
|
||||
}
|
||||
|
||||
public static void setLimitHeight(Region region, double height) {
|
||||
region.setMaxHeight(height);
|
||||
region.setMinHeight(height);
|
||||
region.setPrefHeight(height);
|
||||
}
|
||||
|
||||
public static double getLimitHeight(Region region) {
|
||||
return region.getMaxHeight();
|
||||
}
|
||||
|
||||
public static Node limitingSize(Node node, double width, double height) {
|
||||
StackPane pane = new StackPane(node);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
FXUtils.setLimitWidth(pane, width);
|
||||
FXUtils.setLimitHeight(pane, height);
|
||||
return pane;
|
||||
}
|
||||
|
||||
public static void smoothScrolling(ScrollPane scrollPane) {
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
public static void loadFXML(Node node, String absolutePath) {
|
||||
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), I18n.getResourceBundle());
|
||||
loader.setRoot(node);
|
||||
loader.setController(node);
|
||||
try {
|
||||
loader.load();
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void installFastTooltip(Node node, Tooltip tooltip) {
|
||||
installTooltip(node, 50, 5000, 0, tooltip);
|
||||
}
|
||||
|
||||
public static void installFastTooltip(Node node, String tooltip) {
|
||||
installFastTooltip(node, new Tooltip(tooltip));
|
||||
}
|
||||
|
||||
public static void installSlowTooltip(Node node, Tooltip tooltip) {
|
||||
installTooltip(node, 500, 5000, 0, tooltip);
|
||||
}
|
||||
|
||||
public static void installSlowTooltip(Node node, String tooltip) {
|
||||
installSlowTooltip(node, new Tooltip(tooltip));
|
||||
}
|
||||
|
||||
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
try {
|
||||
// Java 8
|
||||
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
|
||||
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
|
||||
behaviorConstructor.setAccessible(true);
|
||||
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
|
||||
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
|
||||
installMethod.setAccessible(true);
|
||||
installMethod.invoke(behavior, node, tooltip);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
try {
|
||||
// Java 9
|
||||
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
|
||||
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
|
||||
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
|
||||
} catch (ReflectiveOperationException e2) {
|
||||
e.addSuppressed(e2);
|
||||
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
|
||||
}
|
||||
Tooltip.install(node, tooltip);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void openFolder(File file) {
|
||||
if (!FileUtils.makeDirectory(file)) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to make directory " + file);
|
||||
return;
|
||||
}
|
||||
|
||||
String path = file.getAbsolutePath();
|
||||
|
||||
switch (OperatingSystem.CURRENT_OS) {
|
||||
case OSX:
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", path});
|
||||
} catch (IOException e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by executing /usr/bin/open", e);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
thread(() -> {
|
||||
if (java.awt.Desktop.isDesktopSupported()) {
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().open(file);
|
||||
} catch (Throwable e) {
|
||||
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open URL by java.awt.Desktop
|
||||
*
|
||||
* @param link null is allowed but will be ignored
|
||||
*/
|
||||
public static void openLink(String link) {
|
||||
if (link == null)
|
||||
return;
|
||||
thread(() -> {
|
||||
if (java.awt.Desktop.isDesktopSupported()) {
|
||||
try {
|
||||
java.awt.Desktop.getDesktop().browse(new URI(link));
|
||||
} catch (Throwable e) {
|
||||
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
|
||||
try {
|
||||
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", link});
|
||||
} catch (IOException ex) {
|
||||
Logging.LOG.log(Level.WARNING, "Unable to open link: " + link, ex);
|
||||
}
|
||||
Logging.LOG.log(Level.WARNING, "Failed to open link: " + link, e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void bindInt(JFXTextField textField, Property<Number> property) {
|
||||
textField.textProperty().bindBidirectional(property, SafeStringConverter.fromInteger());
|
||||
}
|
||||
|
||||
public static void unbindInt(JFXTextField textField, Property<Number> property) {
|
||||
textField.textProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void bindString(JFXTextField textField, Property<String> property) {
|
||||
textField.textProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void unbindString(JFXTextField textField, Property<String> property) {
|
||||
textField.textProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
|
||||
toggleButton.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().bindBidirectional(property);
|
||||
}
|
||||
|
||||
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
|
||||
checkBox.selectedProperty().unbindBidirectional(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind combo box selection with given enum property bidirectionally.
|
||||
* You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.
|
||||
* @param comboBox the combo box being bound with {@code property}.
|
||||
* @param property the property being bound with {@code combo box}.
|
||||
* @see #unbindEnum(JFXComboBox)
|
||||
* @deprecated Use {@link ExtendedProperties#selectedItemPropertyFor(ComboBox)}
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum<?>> property) {
|
||||
unbindEnum(comboBox);
|
||||
ChangeListener<Number> listener = (a, b, newValue) ->
|
||||
((Property) property).setValue(property.getValue().getClass().getEnumConstants()[newValue.intValue()]);
|
||||
comboBox.getSelectionModel().select(property.getValue().ordinal());
|
||||
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
|
||||
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind combo box selection with given enum property bidirectionally.
|
||||
* You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.
|
||||
* @param comboBox the combo box being bound with the property which can be inferred by {@code bindEnum}.
|
||||
* @see #bindEnum(JFXComboBox, Property)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Deprecated
|
||||
public static void unbindEnum(JFXComboBox<?> comboBox) {
|
||||
ChangeListener listener = tryCast(comboBox.getProperties().get("FXUtils.bindEnum.listener"), ChangeListener.class).orElse(null);
|
||||
if (listener == null) return;
|
||||
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
|
||||
}
|
||||
|
||||
public static void smoothScrolling(ListView<?> listView) {
|
||||
listView.skinProperty().addListener(o -> {
|
||||
ScrollBar bar = (ScrollBar) listView.lookup(".scroll-bar");
|
||||
Node virtualFlow = listView.lookup(".virtual-flow");
|
||||
double[] frictions = new double[]{0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
|
||||
double[] pushes = new double[]{1};
|
||||
double[] derivatives = new double[frictions.length];
|
||||
|
||||
Timeline timeline = new Timeline();
|
||||
bar.addEventHandler(MouseEvent.DRAG_DETECTED, e -> timeline.stop());
|
||||
|
||||
EventHandler<ScrollEvent> scrollEventHandler = event -> {
|
||||
if (event.getEventType() == ScrollEvent.SCROLL) {
|
||||
int direction = event.getDeltaY() > 0 ? -1 : 1;
|
||||
for (int i = 0; i < pushes.length; ++i)
|
||||
derivatives[i] += direction * pushes[i];
|
||||
if (timeline.getStatus() == Animation.Status.STOPPED)
|
||||
timeline.play();
|
||||
event.consume();
|
||||
}
|
||||
};
|
||||
|
||||
bar.addEventHandler(ScrollEvent.ANY, scrollEventHandler);
|
||||
virtualFlow.setOnScroll(scrollEventHandler);
|
||||
|
||||
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), event -> {
|
||||
for (int i = 0; i < derivatives.length; ++i)
|
||||
derivatives[i] *= frictions[i];
|
||||
for (int i = 1; i < derivatives.length; ++i)
|
||||
derivatives[i] += derivatives[i - 1];
|
||||
double dy = derivatives[derivatives.length - 1];
|
||||
double height = listView.getLayoutBounds().getHeight();
|
||||
bar.setValue(Math.min(Math.max(bar.getValue() + dy / height, 0), 1));
|
||||
if (Math.abs(dy) < 0.001)
|
||||
timeline.stop();
|
||||
listView.requestLayout();
|
||||
}));
|
||||
timeline.setCycleCount(Animation.INDEFINITE);
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> T runInUIThread(Supplier<T> supplier) {
|
||||
if (javafx.application.Platform.isFxApplicationThread()) {
|
||||
return supplier.get();
|
||||
} else {
|
||||
CountDownLatch doneLatch = new CountDownLatch(1);
|
||||
AtomicReference<T> reference = new AtomicReference<>();
|
||||
Platform.runLater(() -> {
|
||||
try {
|
||||
reference.set(supplier.get());
|
||||
} finally {
|
||||
doneLatch.countDown();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
try {
|
||||
doneLatch.await();
|
||||
} catch (InterruptedException var3) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
|
||||
return reference.get();
|
||||
}
|
||||
}
|
||||
|
||||
public static void applyDragListener(Node node, FileFilter filter, Consumer<List<File>> callback) {
|
||||
applyDragListener(node, filter, callback, null);
|
||||
}
|
||||
|
||||
public static void applyDragListener(Node node, FileFilter filter, Consumer<List<File>> callback, Runnable dragDropped) {
|
||||
node.setOnDragOver(event -> {
|
||||
if (event.getGestureSource() != node && event.getDragboard().hasFiles()) {
|
||||
if (event.getDragboard().getFiles().stream().anyMatch(filter::accept))
|
||||
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
|
||||
node.setOnDragDropped(event -> {
|
||||
List<File> files = event.getDragboard().getFiles();
|
||||
if (files != null) {
|
||||
List<File> acceptFiles = files.stream().filter(filter::accept).collect(Collectors.toList());
|
||||
if (!acceptFiles.isEmpty()) {
|
||||
callback.accept(acceptFiles);
|
||||
event.setDropCompleted(true);
|
||||
}
|
||||
}
|
||||
if (dragDropped != null)
|
||||
dragDropped.run();
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
|
||||
public static <T> StringConverter<T> stringConverter(Function<T, String> func) {
|
||||
return new StringConverter<T>() {
|
||||
|
||||
@Override
|
||||
public String toString(T object) {
|
||||
return object == null ? "" : func.apply(object);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T fromString(String string) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static <T> Callback<ListView<T>, ListCell<T>> jfxListCellFactory(Function<T, Node> graphicBuilder) {
|
||||
return view -> new JFXListCell<T>() {
|
||||
@Override
|
||||
public void updateItem(T item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (!empty) {
|
||||
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
setGraphic(graphicBuilder.apply(item));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final Interpolator SINE = new Interpolator() {
|
||||
@Override
|
||||
protected double curve(double t) {
|
||||
return Math.sin(t * Math.PI / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Interpolator.SINE";
|
||||
}
|
||||
};
|
||||
|
||||
public static Runnable withJFXPopupClosing(Runnable runnable, JFXPopup popup) {
|
||||
return () -> {
|
||||
runnable.run();
|
||||
popup.hide();
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.6" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jScrollPane2" pref="350" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="btnDownload" max="32767" attributes="0"/>
|
||||
<Component id="btnRefreshGameDownloads" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="btnRefreshGameDownloads" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Component id="btnDownload" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="jScrollPane2" alignment="0" pref="300" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="btnDownload">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.button.download" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnDownloadActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane2">
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTable" name="lstDownloads">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="SwingUtils.makeDefaultTableModel(new String[]{C.i18n("install.version"), C.i18n("install.release_time"), C.i18n("install.time"), C.i18n("install.type")},new Class[]{String.class, String.class, String.class, String.class}, new boolean[]{false, false, false, false})" type="code"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" value=""/>
|
||||
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
|
||||
<JTableSelectionModel selectionMode="0"/>
|
||||
</Property>
|
||||
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
|
||||
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="btnRefreshGameDownloads">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.button.refresh" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnRefreshGameDownloadsActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 org.jackhuang.hmcl.util.ui.Page;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import org.jackhuang.hmcl.api.ui.TopTabPage;
|
||||
import org.jackhuang.hmcl.core.download.MinecraftRemoteVersions;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.util.task.ProgressProviderListener;
|
||||
import org.jackhuang.hmcl.util.task.Task;
|
||||
import org.jackhuang.hmcl.util.task.TaskWindow;
|
||||
import org.jackhuang.hmcl.util.ui.SwingUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class GameDownloadPanel extends Page implements ProgressProviderListener {
|
||||
|
||||
GameSettingsPanel gsp;
|
||||
|
||||
/**
|
||||
* Creates new form GameDownloadPanel
|
||||
*/
|
||||
public GameDownloadPanel(GameSettingsPanel gsp) {
|
||||
initComponents();
|
||||
this.gsp = gsp;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
btnDownload = new javax.swing.JButton();
|
||||
jScrollPane2 = new javax.swing.JScrollPane();
|
||||
lstDownloads = new javax.swing.JTable();
|
||||
btnRefreshGameDownloads = new javax.swing.JButton();
|
||||
|
||||
btnDownload.setText(C.i18n("ui.button.download")); // NOI18N
|
||||
btnDownload.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnDownloadActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lstDownloads.setModel(SwingUtils.makeDefaultTableModel(new String[]{C.i18n("install.version"), C.i18n("install.release_time"), C.i18n("install.time"), C.i18n("install.type")},new Class[]{String.class, String.class, String.class, String.class}, new boolean[]{false, false, false, false}));
|
||||
lstDownloads.setToolTipText("");
|
||||
lstDownloads.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
|
||||
jScrollPane2.setViewportView(lstDownloads);
|
||||
|
||||
btnRefreshGameDownloads.setText(C.i18n("ui.button.refresh")); // NOI18N
|
||||
btnRefreshGameDownloads.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnRefreshGameDownloadsActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 350, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(btnDownload, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(btnRefreshGameDownloads, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(btnRefreshGameDownloads)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(btnDownload))
|
||||
.addComponent(jScrollPane2, javax.swing.GroupLayout.DEFAULT_SIZE, 300, Short.MAX_VALUE)
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void btnDownloadActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnDownloadActionPerformed
|
||||
downloadMinecraft();
|
||||
gsp.refreshVersions();
|
||||
}//GEN-LAST:event_btnDownloadActionPerformed
|
||||
|
||||
private void btnRefreshGameDownloadsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRefreshGameDownloadsActionPerformed
|
||||
refreshDownloads();
|
||||
}//GEN-LAST:event_btnRefreshGameDownloadsActionPerformed
|
||||
|
||||
public void refreshDownloads() {
|
||||
if (loading)
|
||||
return;
|
||||
loading = true;
|
||||
DefaultTableModel model = SwingUtils.clearDefaultTable(lstDownloads);
|
||||
model.addRow(new Object[] { C.i18n("message.loading"), "", "" });
|
||||
MinecraftRemoteVersions.refreshRomoteVersions(Settings.getLastProfile().service().getDownloadType())
|
||||
.reg((ver) -> model.addRow(new Object[] { ver.id, ver.releaseTime, ver.time,
|
||||
StrUtils.equalsOne(ver.type, "old_beta", "old_alpha", "release", "snapshot") ? C.i18n("versions." + ver.type) : ver.type }))
|
||||
.regDone(SwingUtils.invokeLater(() -> {
|
||||
loading = false;
|
||||
lstDownloads.requestFocus();
|
||||
if (model.getRowCount() > 0)
|
||||
model.removeRow(0);
|
||||
})).setProgressProviderListener(this).runAsync();
|
||||
}
|
||||
|
||||
boolean loading = false;
|
||||
|
||||
@Override
|
||||
public void setProgress(Task task, int prog, int max) {
|
||||
DefaultTableModel model = (DefaultTableModel) lstDownloads.getModel();
|
||||
if (model.getRowCount() > 0)
|
||||
model.setValueAt(C.i18n("message.loading") + " " + (prog < 0 ? "???" : Integer.toString(prog * 100 / max) + "%"), 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(Task task, String sta) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressProviderDone(Task task) {
|
||||
}
|
||||
|
||||
void downloadMinecraft() {
|
||||
if (lstDownloads.getSelectedRow() < 0) {
|
||||
MessageBox.show(C.i18n("gamedownload.not_refreshed"));
|
||||
return;
|
||||
}
|
||||
String id = (String) lstDownloads.getModel().getValueAt(lstDownloads.getSelectedRow(), 0);
|
||||
TaskWindow.factory().execute(Settings.getLastProfile().service().download().downloadMinecraft(id));
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton btnDownload;
|
||||
private javax.swing.JButton btnRefreshGameDownloads;
|
||||
private javax.swing.JScrollPane jScrollPane2;
|
||||
private javax.swing.JTable lstDownloads;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
boolean refreshedDownloads = false;
|
||||
|
||||
@Override
|
||||
public void onSelect(TopTabPage page) {
|
||||
super.onSelect(page);
|
||||
if (!refreshedDownloads) {
|
||||
refreshedDownloads = true;
|
||||
refreshDownloads();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1017
File diff suppressed because it is too large.
Load diff
+1532
File diff suppressed because it is too large.
Load diff
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 java.awt.EventQueue;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.DefaultButtonModel;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class HeaderTab extends JLabel
|
||||
implements MouseListener {
|
||||
|
||||
private boolean isActive;
|
||||
private final DefaultButtonModel model;
|
||||
|
||||
public HeaderTab(String text) {
|
||||
super(text);
|
||||
|
||||
this.model = new DefaultButtonModel();
|
||||
setIsActive(false);
|
||||
|
||||
setBorder(BorderFactory.createEmptyBorder(6, 18, 7, 18));
|
||||
addMouseListener(this);
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return this.isActive;
|
||||
}
|
||||
|
||||
public final void setIsActive(boolean isActive) {
|
||||
this.isActive = isActive;
|
||||
setOpaque(isActive);
|
||||
|
||||
EventQueue.invokeLater(HeaderTab.this::repaint);
|
||||
}
|
||||
|
||||
public void addActionListener(ActionListener listener) {
|
||||
this.model.addActionListener(listener);
|
||||
}
|
||||
|
||||
public String getActionCommand() {
|
||||
return this.model.getActionCommand();
|
||||
}
|
||||
|
||||
public ActionListener[] getActionListeners() {
|
||||
return this.model.getActionListeners();
|
||||
}
|
||||
|
||||
public void setActionCommand(String command) {
|
||||
this.model.setActionCommand(command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (!isEnabled())
|
||||
return;
|
||||
this.model.setPressed(true);
|
||||
this.model.setArmed(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (!isEnabled())
|
||||
return;
|
||||
this.model.setPressed(false);
|
||||
this.model.setArmed(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
if (!isEnabled())
|
||||
return;
|
||||
this.model.setRollover(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
if (!isEnabled())
|
||||
return;
|
||||
this.model.setRollover(false);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class InstallerItem extends BorderPane {
|
||||
private final Consumer<InstallerItem> deleteCallback;
|
||||
|
||||
@FXML
|
||||
private Label lblInstallerArtifact;
|
||||
|
||||
@FXML
|
||||
private Label lblInstallerVersion;
|
||||
|
||||
public InstallerItem(String artifact, String version, Consumer<InstallerItem> deleteCallback) {
|
||||
this.deleteCallback = deleteCallback;
|
||||
FXUtils.loadFXML(this, "/assets/fxml/version/installer-item.fxml");
|
||||
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblInstallerArtifact.setText(artifact);
|
||||
lblInstallerVersion.setText(i18n("archive.version") + ": " + version);
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onDelete() {
|
||||
deleteCallback.accept(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.6" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jScrollPane12" pref="292" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="btnRefresh" max="32767" attributes="0"/>
|
||||
<Component id="btnInstall" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="jScrollPane12" pref="147" max="32767" attributes="0"/>
|
||||
<Group type="102" attributes="0">
|
||||
<Component id="btnInstall" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnRefresh" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="btnInstall">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.button.install" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnInstallActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="jScrollPane12">
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTable" name="lstInstallers">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="SwingUtils.makeDefaultTableModel(new String[]{C.i18n("install.version"), C.i18n("install.mcversion")},
 new Class[]{String.class, String.class}, new boolean[]{false, false})" type="code"/>
|
||||
</Property>
|
||||
<Property name="selectionModel" type="javax.swing.ListSelectionModel" editor="org.netbeans.modules.form.editors2.JTableSelectionModelEditor">
|
||||
<JTableSelectionModel selectionMode="0"/>
|
||||
</Property>
|
||||
<Property name="tableHeader" type="javax.swing.table.JTableHeader" editor="org.netbeans.modules.form.editors2.JTableHeaderEditor">
|
||||
<TableHeader reorderingAllowed="true" resizingAllowed="true"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="btnRefresh">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.button.refresh" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnRefreshActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 org.jackhuang.hmcl.util.ui.Page;
|
||||
import java.util.List;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import org.jackhuang.hmcl.api.ui.TopTabPage;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.core.install.InstallerType;
|
||||
import org.jackhuang.hmcl.core.install.InstallerVersionList;
|
||||
import org.jackhuang.hmcl.util.task.TaskRunnable;
|
||||
import org.jackhuang.hmcl.util.task.TaskWindow;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.util.task.ProgressProviderListener;
|
||||
import org.jackhuang.hmcl.util.task.Task;
|
||||
import org.jackhuang.hmcl.util.ui.SwingUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class InstallerPanel extends Page implements ProgressProviderListener {
|
||||
|
||||
GameSettingsPanel gsp;
|
||||
|
||||
/**
|
||||
* Creates new form InstallerPanel
|
||||
*
|
||||
* @param gsp To get the minecraft version
|
||||
* @param installerType load which installer
|
||||
*/
|
||||
public InstallerPanel(GameSettingsPanel gsp, InstallerType installerType) {
|
||||
initComponents();
|
||||
|
||||
animationEnabled = Settings.getInstance().isEnableAnimation();
|
||||
|
||||
setOpaque(false);
|
||||
this.gsp = gsp;
|
||||
id = installerType;
|
||||
list = Settings.getInstance().getDownloadSource().getProvider().getInstallerByType(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
btnInstall = new javax.swing.JButton();
|
||||
jScrollPane12 = new javax.swing.JScrollPane();
|
||||
lstInstallers = new javax.swing.JTable();
|
||||
btnRefresh = new javax.swing.JButton();
|
||||
|
||||
btnInstall.setText(C.i18n("ui.button.install")); // NOI18N
|
||||
btnInstall.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnInstallActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lstInstallers.setModel(SwingUtils.makeDefaultTableModel(new String[]{C.i18n("install.version"), C.i18n("install.mcversion")},
|
||||
new Class[]{String.class, String.class}, new boolean[]{false, false}));
|
||||
lstInstallers.setSelectionMode(javax.swing.ListSelectionModel.SINGLE_SELECTION);
|
||||
jScrollPane12.setViewportView(lstInstallers);
|
||||
|
||||
btnRefresh.setText(C.i18n("ui.button.refresh")); // NOI18N
|
||||
btnRefresh.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnRefreshActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jScrollPane12, javax.swing.GroupLayout.DEFAULT_SIZE, 292, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(btnRefresh, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(btnInstall, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(jScrollPane12, javax.swing.GroupLayout.DEFAULT_SIZE, 147, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(btnInstall)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnRefresh)
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void btnInstallActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnInstallActionPerformed
|
||||
downloadSelectedRow();
|
||||
}//GEN-LAST:event_btnInstallActionPerformed
|
||||
|
||||
private void btnRefreshActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRefreshActionPerformed
|
||||
refreshVersions();
|
||||
}//GEN-LAST:event_btnRefreshActionPerformed
|
||||
|
||||
transient List<InstallerVersionList.InstallerVersion> versions;
|
||||
InstallerVersionList list;
|
||||
InstallerType id;
|
||||
|
||||
void refreshVersions() {
|
||||
if (loading)
|
||||
return;
|
||||
Task t = list.refresh(new String[] { gsp.getMinecraftVersionFormatted() });
|
||||
if (t != null) {
|
||||
loading = true;
|
||||
DefaultTableModel model = SwingUtils.clearDefaultTable(lstInstallers);
|
||||
model.addRow(new Object[] { C.i18n("message.loading"), "", "" });
|
||||
t.with(new TaskRunnable(this::loadVersions)).setProgressProviderListener(this).runAsync();
|
||||
}
|
||||
}
|
||||
|
||||
boolean loading = false;
|
||||
|
||||
@Override
|
||||
public void setProgress(Task task, int prog, int max) {
|
||||
DefaultTableModel model = (DefaultTableModel) lstInstallers.getModel();
|
||||
if (model.getRowCount() > 0)
|
||||
model.setValueAt(C.i18n("message.loading") + " " + (prog < 0 ? "???" : Integer.toString(prog * 100 / max) + "%"), 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStatus(Task task, String sta) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressProviderDone(Task task) {
|
||||
loading = false;
|
||||
DefaultTableModel model = (DefaultTableModel) lstInstallers.getModel();
|
||||
if (model.getRowCount() > 0)
|
||||
model.removeRow(0);
|
||||
}
|
||||
|
||||
public synchronized InstallerVersionList.InstallerVersion getVersion(int idx) {
|
||||
return versions.get(idx);
|
||||
}
|
||||
|
||||
synchronized void downloadSelectedRow() {
|
||||
int idx = lstInstallers.getSelectedRow();
|
||||
if (versions == null || idx < 0 || idx >= versions.size()) {
|
||||
MessageBox.show(C.i18n("install.not_refreshed"));
|
||||
return;
|
||||
}
|
||||
TaskWindow.factory()
|
||||
.append(Settings.getLastProfile().service().install().download(Settings.getLastProfile().getSelectedVersion(), getVersion(idx), id))
|
||||
.append(new TaskRunnable(() -> gsp.refreshVersions()))
|
||||
.execute();
|
||||
}
|
||||
|
||||
public void loadVersions() {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
synchronized (InstallerPanel.this) {
|
||||
DefaultTableModel model = (DefaultTableModel) lstInstallers.getModel();
|
||||
String mcver = StrUtils.formatVersion(gsp.getMinecraftVersionFormatted());
|
||||
versions = list.getVersions(mcver);
|
||||
SwingUtils.clearDefaultTable(lstInstallers);
|
||||
if (versions != null)
|
||||
for (InstallerVersionList.InstallerVersion v : versions)
|
||||
if (v != null)
|
||||
model.addRow(new Object[] { v.selfVersion == null ? "null" : v.selfVersion, v.mcVersion == null ? "null" : v.mcVersion });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
boolean refreshed = false;
|
||||
|
||||
@Override
|
||||
public void onSelect(TopTabPage page) {
|
||||
super.onSelect(page);
|
||||
if (!refreshed) {
|
||||
refreshVersions();
|
||||
refreshed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton btnInstall;
|
||||
private javax.swing.JButton btnRefresh;
|
||||
private javax.swing.JScrollPane jScrollPane12;
|
||||
private javax.swing.JTable lstInstallers;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 javax.swing.JTextPane;
|
||||
import javax.swing.text.AbstractDocument;
|
||||
import javax.swing.text.BoxView;
|
||||
import javax.swing.text.ComponentView;
|
||||
import javax.swing.text.Element;
|
||||
import javax.swing.text.IconView;
|
||||
import javax.swing.text.LabelView;
|
||||
import javax.swing.text.ParagraphView;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyledEditorKit;
|
||||
import javax.swing.text.View;
|
||||
import javax.swing.text.ViewFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public class JLineWrapTextPane extends JTextPane {
|
||||
|
||||
// 内部类
|
||||
// 以下内部类全都用于实现自动强制折行
|
||||
|
||||
private class WarpEditorKit extends StyledEditorKit {
|
||||
|
||||
private ViewFactory defaultFactory = new WarpColumnFactory();
|
||||
|
||||
@Override
|
||||
public ViewFactory getViewFactory() {
|
||||
return defaultFactory;
|
||||
}
|
||||
}
|
||||
|
||||
private class WarpColumnFactory implements ViewFactory {
|
||||
|
||||
public View create(Element elem) {
|
||||
String kind = elem.getName();
|
||||
if (kind != null) {
|
||||
if (kind.equals(AbstractDocument.ContentElementName)) {
|
||||
return new WarpLabelView(elem);
|
||||
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
|
||||
return new ParagraphView(elem);
|
||||
} else if (kind.equals(AbstractDocument.SectionElementName)) {
|
||||
return new BoxView(elem, View.Y_AXIS);
|
||||
} else if (kind.equals(StyleConstants.ComponentElementName)) {
|
||||
return new ComponentView(elem);
|
||||
} else if (kind.equals(StyleConstants.IconElementName)) {
|
||||
return new IconView(elem);
|
||||
}
|
||||
}
|
||||
|
||||
// default to text display
|
||||
return new LabelView(elem);
|
||||
}
|
||||
}
|
||||
|
||||
private class WarpLabelView extends LabelView {
|
||||
|
||||
public WarpLabelView(Element elem) {
|
||||
super(elem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getMinimumSpan(int axis) {
|
||||
switch (axis) {
|
||||
case View.X_AXIS:
|
||||
return 0;
|
||||
case View.Y_AXIS:
|
||||
return super.getMinimumSpan(axis);
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid axis: " + axis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 本类
|
||||
|
||||
// 构造函数
|
||||
public JLineWrapTextPane() {
|
||||
super();
|
||||
this.setEditorKit(new WarpEditorKit());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.3" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace min="-2" pref="10" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="lblModpack" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblRestart" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="tabLauncherSettings" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="tabLauncherSettings" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblModpack" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblRestart" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="lblModpack">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.modpack" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||
<Color id="手型光标"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="lblModpackMouseClicked"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblRestart">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.restart" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Container class="javax.swing.JTabbedPane" name="tabLauncherSettings">
|
||||
<AccessibilityProperties>
|
||||
<Property name="AccessibleContext.accessibleName" type="java.lang.String" value=""/>
|
||||
</AccessibilityProperties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new NewTabPane()"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="pnlGeneral">
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation())"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="General">
|
||||
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.tab.general" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="btnCheckUpdate" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnMCBBS" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="lblDownloadSource" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblCommonPath" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblLang" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblProxy" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace min="-2" pref="28" max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="cboProxyType" pref="156" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblProxyHost" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="txtProxyHost" min="-2" pref="80" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblProxyPort" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="txtProxyPort" min="-2" pref="80" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblProxyUserName" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="txtProxyUsername" min="-2" pref="80" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblProxyPassword" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="txtProxyPassword" min="-2" pref="80" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="cboLang" alignment="1" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="txtCommonPath" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnSetCommonPath" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="cboDownloadSource" alignment="1" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblCommonPath" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="txtCommonPath" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="btnSetCommonPath" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblDownloadSource" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="cboDownloadSource" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="cboLang" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="lblLang" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="txtProxyHost" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="lblProxy" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblProxyHost" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="txtProxyPort" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="lblProxyPort" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="txtProxyUsername" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="lblProxyUserName" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="txtProxyPassword" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="lblProxyPassword" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="cboProxyType" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="69" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="btnCheckUpdate" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="btnMCBBS" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="lblProxy">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.proxy" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblProxyHost">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="proxy.host" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="txtProxyHost">
|
||||
<Events>
|
||||
<EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtProxyHostFocusLost"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblProxyPort">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="proxy.port" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="txtProxyPort">
|
||||
<Events>
|
||||
<EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtProxyPortFocusLost"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblProxyUserName">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="proxy.username" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="txtProxyUsername">
|
||||
<Events>
|
||||
<EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtProxyUsernameFocusLost"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblProxyPassword">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="proxy.password" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="txtProxyPassword">
|
||||
<Events>
|
||||
<EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtProxyPasswordFocusLost"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboDownloadSource">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboDownloadSourceItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblDownloadSource">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.download_source" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblCommonPath">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.common_location" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="txtCommonPath">
|
||||
<Properties>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.commpath_tooltip" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtCommonPathFocusLost"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnSetCommonPath">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.button.explore" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnSetCommonPathActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnCheckUpdate">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.update_launcher" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnCheckUpdateActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnMCBBS">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="MCBBS"/>
|
||||
<Property name="toolTipText" type="java.lang.String" value=""/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnMCBBSActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboLang">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<Connection code="new DefaultComboBoxModel(new String[]{C.i18n("color.blue"),C.i18n("color.green"),C.i18n("color.purple"),C.i18n("color.dark_blue"),C.i18n("color.orange"),C.i18n("color.red")})" type="code"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboLangItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblLang">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.lang" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboProxyType">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="3">
|
||||
<StringItem index="0" value="Direct"/>
|
||||
<StringItem index="1" value="HTTP"/>
|
||||
<StringItem index="2" value="Socks"/>
|
||||
</StringArray>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboProxyTypeItemStateChanged"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="pnlUI">
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation())"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="UI">
|
||||
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.tab.ui" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="chkDecorated" min="-2" max="-2" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="chkEnableShadow" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="chkEnableBlur" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="chkEnableAnimation" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace min="0" pref="163" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="lblBackground" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblFont" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblTheme" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="cboTheme" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="txtBackgroundPath" max="32767" attributes="0"/>
|
||||
<Component id="cboFontFamily" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" max="-2" attributes="0">
|
||||
<Component id="btnSelBackgroundPath" max="32767" attributes="0"/>
|
||||
<Component id="spinnerFontSize" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="lblExample" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblBackground" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="txtBackgroundPath" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="btnSelBackgroundPath" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="cboFontFamily" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="lblFont" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="spinnerFontSize" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblExample" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="cboTheme" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="lblTheme" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace pref="63" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="chkEnableShadow" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="chkEnableBlur" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="chkEnableAnimation" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="chkDecorated" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="lblTheme">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.theme" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboTheme">
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboThemeItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblBackground">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.background_location" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" value=""/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="txtBackgroundPath">
|
||||
<Properties>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.background_tooltip" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtBackgroundPathFocusLost"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="chkEnableShadow">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.enable_shadow" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="chkEnableShadowItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="chkEnableBlur">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.enable_blur" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="chkEnableBlurItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="chkEnableAnimation">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.enable_animation" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="chkEnableAnimationItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JCheckBox" name="chkDecorated">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.decorated" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="chkDecoratedItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnSelBackgroundPath">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.button.explore" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnSelBackgroundPathActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboFontFamily">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
|
||||
<SerializedValue value="-84,-19,0,5,115,114,0,32,106,97,118,97,120,46,115,119,105,110,103,46,68,101,102,97,117,108,116,67,111,109,98,111,66,111,120,77,111,100,101,108,92,-10,105,-80,-61,-104,-56,48,2,0,2,76,0,7,111,98,106,101,99,116,115,116,0,18,76,106,97,118,97,47,117,116,105,108,47,86,101,99,116,111,114,59,76,0,14,115,101,108,101,99,116,101,100,79,98,106,101,99,116,116,0,18,76,106,97,118,97,47,108,97,110,103,47,79,98,106,101,99,116,59,120,114,0,29,106,97,118,97,120,46,115,119,105,110,103,46,65,98,115,116,114,97,99,116,76,105,115,116,77,111,100,101,108,87,-79,61,-30,108,45,-87,-39,2,0,1,76,0,12,108,105,115,116,101,110,101,114,76,105,115,116,116,0,37,76,106,97,118,97,120,47,115,119,105,110,103,47,101,118,101,110,116,47,69,118,101,110,116,76,105,115,116,101,110,101,114,76,105,115,116,59,120,112,115,114,0,35,106,97,118,97,120,46,115,119,105,110,103,46,101,118,101,110,116,46,69,118,101,110,116,76,105,115,116,101,110,101,114,76,105,115,116,-79,54,-58,125,-124,-22,-42,68,3,0,0,120,112,116,0,34,106,97,118,97,120,46,115,119,105,110,103,46,101,118,101,110,116,46,76,105,115,116,68,97,116,97,76,105,115,116,101,110,101,114,115,114,0,21,106,97,118,97,120,46,115,119,105,110,103,46,74,67,111,109,98,111,66,111,120,115,-25,-16,-68,-83,109,105,-54,3,0,14,90,0,17,102,105,114,105,110,103,65,99,116,105,111,110,69,118,101,110,116,90,0,10,105,115,69,100,105,116,97,98,108,101,90,0,23,108,105,103,104,116,87,101,105,103,104,116,80,111,112,117,112,69,110,97,98,108,101,100,73,0,15,109,97,120,105,109,117,109,82,111,119,67,111,117,110,116,90,0,13,115,101,108,101,99,116,105,110,103,73,116,101,109,76,0,6,97,99,116,105,111,110,116,0,20,76,106,97,118,97,120,47,115,119,105,110,103,47,65,99,116,105,111,110,59,76,0,13,97,99,116,105,111,110,67,111,109,109,97,110,100,116,0,18,76,106,97,118,97,47,108,97,110,103,47,83,116,114,105,110,103,59,76,0,28,97,99,116,105,111,110,80,114,111,112,101,114,116,121,67,104,97,110,103,101,76,105,115,116,101,110,101,114,116,0,35,76,106,97,118,97,47,98,101,97,110,115,47,80,114,111,112,101,114,116,121,67,104,97,110,103,101,76,105,115,116,101,110,101,114,59,76,0,9,100,97,116,97,77,111,100,101,108,116,0,27,76,106,97,118,97,120,47,115,119,105,110,103,47,67,111,109,98,111,66,111,120,77,111,100,101,108,59,76,0,6,101,100,105,116,111,114,116,0,28,76,106,97,118,97,120,47,115,119,105,110,103,47,67,111,109,98,111,66,111,120,69,100,105,116,111,114,59,76,0,19,107,101,121,83,101,108,101,99,116,105,111,110,77,97,110,97,103,101,114,116,0,43,76,106,97,118,97,120,47,115,119,105,110,103,47,74,67,111,109,98,111,66,111,120,36,75,101,121,83,101,108,101,99,116,105,111,110,77,97,110,97,103,101,114,59,76,0,21,112,114,111,116,111,116,121,112,101,68,105,115,112,108,97,121,86,97,108,117,101,113,0,126,0,2,76,0,8,114,101,110,100,101,114,101,114,116,0,30,76,106,97,118,97,120,47,115,119,105,110,103,47,76,105,115,116,67,101,108,108,82,101,110,100,101,114,101,114,59,76,0,20,115,101,108,101,99,116,101,100,73,116,101,109,82,101,109,105,110,100,101,114,113,0,126,0,2,120,114,0,22,106,97,118,97,120,46,115,119,105,110,103,46,74,67,111,109,112,111,110,101,110,116,51,-17,99,38,30,108,-90,-113,3,0,16,70,0,10,97,108,105,103,110,109,101,110,116,88,70,0,10,97,108,105,103,110,109,101,110,116,89,90,0,11,97,117,116,111,115,99,114,111,108,108,115,73,0,5,102,108,97,103,115,90,0,15,105,115,65,108,105,103,110,109,101,110,116,88,83,101,116,90,0,15,105,115,65,108,105,103,110,109,101,110,116,89,83,101,116,90,0,26,118,101,114,105,102,121,73,110,112,117,116,87,104,101,110,70,111,99,117,115,84,97,114,103,101,116,76,0,9,97,99,116,105,111,110,77,97,112,116,0,23,76,106,97,118,97,120,47,115,119,105,110,103,47,65,99,116,105,111,110,77,97,112,59,76,0,16,97,110,99,101,115,116,111,114,73,110,112,117,116,77,97,112,116,0,22,76,106,97,118,97,120,47,115,119,105,110,103,47,73,110,112,117,116,77,97,112,59,76,0,6,98,111,114,100,101,114,116,0,27,76,106,97,118,97,120,47,115,119,105,110,103,47,98,111,114,100,101,114,47,66,111,114,100,101,114,59,76,0,13,102,111,99,117,115,73,110,112,117,116,77,97,112,113,0,126,0,19,76,0,13,105,110,112,117,116,86,101,114,105,102,105,101,114,116,0,27,76,106,97,118,97,120,47,115,119,105,110,103,47,73,110,112,117,116,86,101,114,105,102,105,101,114,59,76,0,12,108,105,115,116,101,110,101,114,76,105,115,116,113,0,126,0,4,76,0,9,112,111,112,117,112,77,101,110,117,116,0,24,76,106,97,118,97,120,47,115,119,105,110,103,47,74,80,111,112,117,112,77,101,110,117,59,76,0,21,118,101,116,111,97,98,108,101,67,104,97,110,103,101,83,117,112,112,111,114,116,116,0,34,76,106,97,118,97,47,98,101,97,110,115,47,86,101,116,111,97,98,108,101,67,104,97,110,103,101,83,117,112,112,111,114,116,59,76,0,14,119,105,110,100,111,119,73,110,112,117,116,77,97,112,116,0,31,76,106,97,118,97,120,47,115,119,105,110,103,47,67,111,109,112,111,110,101,110,116,73,110,112,117,116,77,97,112,59,120,114,0,18,106,97,118,97,46,97,119,116,46,67,111,110,116,97,105,110,101,114,64,7,-128,115,-3,20,12,39,3,0,8,73,0,30,99,111,110,116,97,105,110,101,114,83,101,114,105,97,108,105,122,101,100,68,97,116Line truncated
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboFontFamilyItemStateChanged"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new JFontComboBox()"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblFont">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.log_font" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JSpinner" name="spinnerFontSize">
|
||||
<Events>
|
||||
<EventHandler event="stateChanged" listener="javax.swing.event.ChangeListener" parameters="javax.swing.event.ChangeEvent" handler="spinnerFontSizeStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblExample">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="19:10:10 [Client Thread/INFO]: Example"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="pnlAbout">
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation())"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
|
||||
<JTabbedPaneConstraints tabName="About">
|
||||
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.tab.about" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</JTabbedPaneConstraints>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblAbout" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="252" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblAbout" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="lblAbout">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.about" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
|
||||
<Color id="默认光标"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
@@ -0,0 +1,672 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.api.ui.Theme;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.core.download.DownloadType;
|
||||
import org.jackhuang.hmcl.util.MathUtils;
|
||||
import org.jackhuang.hmcl.util.sys.IOUtils;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
import org.jackhuang.hmcl.util.lang.SupportedLocales;
|
||||
import org.jackhuang.hmcl.util.ui.JFontComboBox;
|
||||
import org.jackhuang.hmcl.util.ui.JSystemFileChooser;
|
||||
import org.jackhuang.hmcl.util.ui.Page;
|
||||
import org.jackhuang.hmcl.util.ui.SwingUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class LauncherSettingsPanel extends RepaintPage {
|
||||
|
||||
public LauncherSettingsPanel() {
|
||||
setRepainter(this);
|
||||
}
|
||||
|
||||
void initGui() {
|
||||
initComponents();
|
||||
|
||||
animationEnabled = Settings.getInstance().isEnableAnimation();
|
||||
|
||||
DefaultComboBoxModel<String> d = new DefaultComboBoxModel<>();
|
||||
for (DownloadType type : DownloadType.values())
|
||||
d.addElement(type.getName());
|
||||
cboDownloadSource.setModel(d);
|
||||
|
||||
d = new DefaultComboBoxModel<>();
|
||||
int id = 0;
|
||||
for (SupportedLocales type : SupportedLocales.values()) {
|
||||
d.addElement(type.showString());
|
||||
if (type.name().equals(Settings.getInstance().getLocalization()))
|
||||
id = type.ordinal();
|
||||
}
|
||||
cboLang.setModel(d);
|
||||
cboLang.setSelectedIndex(id);
|
||||
|
||||
DefaultComboBoxModel<Theme> g = new DefaultComboBoxModel<>();
|
||||
for (Theme t : Theme.THEMES.values())
|
||||
g.addElement(t);
|
||||
cboTheme.setModel(g);
|
||||
|
||||
txtBackgroundPath.setText(Settings.getInstance().getBgpath());
|
||||
txtCommonPath.setText(Settings.getInstance().getCommonpath());
|
||||
txtProxyHost.setText(Settings.getInstance().getProxyHost());
|
||||
txtProxyPort.setText(Settings.getInstance().getProxyPort());
|
||||
txtProxyUsername.setText(Settings.getInstance().getProxyUserName());
|
||||
txtProxyPassword.setText(Settings.getInstance().getProxyPassword());
|
||||
spinnerFontSize.setValue(Settings.getInstance().getFontSize());
|
||||
cboFontFamily.setSelectedItem(Settings.getInstance().getFontFamily());
|
||||
cboDownloadSource.setSelectedIndex(Settings.getInstance().getDownloadType());
|
||||
cboProxyType.setSelectedIndex(Settings.getInstance().getProxyType().ordinal());
|
||||
cboTheme.setSelectedItem(Settings.getInstance().getTheme());
|
||||
chkEnableShadow.setSelected(Settings.getInstance().isEnableShadow());
|
||||
chkEnableBlur.setSelected(Settings.getInstance().isEnableBlur());
|
||||
chkEnableAnimation.setSelected(Settings.getInstance().isEnableAnimation());
|
||||
chkDecorated.setSelected(Settings.getInstance().isDecorated());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
initGui();
|
||||
super.onCreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
lblModpack = new javax.swing.JLabel();
|
||||
lblRestart = new javax.swing.JLabel();
|
||||
tabLauncherSettings = new NewTabPane();
|
||||
pnlGeneral = new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation());
|
||||
lblProxy = new javax.swing.JLabel();
|
||||
lblProxyHost = new javax.swing.JLabel();
|
||||
txtProxyHost = new javax.swing.JTextField();
|
||||
lblProxyPort = new javax.swing.JLabel();
|
||||
txtProxyPort = new javax.swing.JTextField();
|
||||
lblProxyUserName = new javax.swing.JLabel();
|
||||
txtProxyUsername = new javax.swing.JTextField();
|
||||
lblProxyPassword = new javax.swing.JLabel();
|
||||
txtProxyPassword = new javax.swing.JTextField();
|
||||
cboDownloadSource = new javax.swing.JComboBox();
|
||||
lblDownloadSource = new javax.swing.JLabel();
|
||||
lblCommonPath = new javax.swing.JLabel();
|
||||
txtCommonPath = new javax.swing.JTextField();
|
||||
btnSetCommonPath = new javax.swing.JButton();
|
||||
btnCheckUpdate = new javax.swing.JButton();
|
||||
btnMCBBS = new javax.swing.JButton();
|
||||
cboLang = new javax.swing.JComboBox();
|
||||
lblLang = new javax.swing.JLabel();
|
||||
cboProxyType = new javax.swing.JComboBox<>();
|
||||
pnlUI = new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation());
|
||||
lblTheme = new javax.swing.JLabel();
|
||||
cboTheme = new javax.swing.JComboBox();
|
||||
lblBackground = new javax.swing.JLabel();
|
||||
txtBackgroundPath = new javax.swing.JTextField();
|
||||
chkEnableShadow = new javax.swing.JCheckBox();
|
||||
chkEnableBlur = new javax.swing.JCheckBox();
|
||||
chkEnableAnimation = new javax.swing.JCheckBox();
|
||||
chkDecorated = new javax.swing.JCheckBox();
|
||||
btnSelBackgroundPath = new javax.swing.JButton();
|
||||
cboFontFamily = new JFontComboBox();
|
||||
lblFont = new javax.swing.JLabel();
|
||||
spinnerFontSize = new javax.swing.JSpinner();
|
||||
lblExample = new javax.swing.JLabel();
|
||||
pnlAbout = new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation());
|
||||
lblAbout = new javax.swing.JLabel();
|
||||
|
||||
lblModpack.setText(C.i18n("launcher.modpack")); // NOI18N
|
||||
lblModpack.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
|
||||
lblModpack.addMouseListener(new java.awt.event.MouseAdapter() {
|
||||
public void mouseClicked(java.awt.event.MouseEvent evt) {
|
||||
lblModpackMouseClicked(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblRestart.setText(C.i18n("launcher.restart")); // NOI18N
|
||||
|
||||
lblProxy.setText(C.i18n("launcher.proxy")); // NOI18N
|
||||
|
||||
lblProxyHost.setText(C.i18n("proxy.host")); // NOI18N
|
||||
|
||||
txtProxyHost.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusLost(java.awt.event.FocusEvent evt) {
|
||||
txtProxyHostFocusLost(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblProxyPort.setText(C.i18n("proxy.port")); // NOI18N
|
||||
|
||||
txtProxyPort.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusLost(java.awt.event.FocusEvent evt) {
|
||||
txtProxyPortFocusLost(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblProxyUserName.setText(C.i18n("proxy.username")); // NOI18N
|
||||
|
||||
txtProxyUsername.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusLost(java.awt.event.FocusEvent evt) {
|
||||
txtProxyUsernameFocusLost(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblProxyPassword.setText(C.i18n("proxy.password")); // NOI18N
|
||||
|
||||
txtProxyPassword.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusLost(java.awt.event.FocusEvent evt) {
|
||||
txtProxyPasswordFocusLost(evt);
|
||||
}
|
||||
});
|
||||
|
||||
cboDownloadSource.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboDownloadSourceItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblDownloadSource.setText(C.i18n("launcher.download_source")); // NOI18N
|
||||
|
||||
lblCommonPath.setText(C.i18n("launcher.common_location")); // NOI18N
|
||||
|
||||
txtCommonPath.setToolTipText(C.i18n("launcher.commpath_tooltip")); // NOI18N
|
||||
txtCommonPath.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusLost(java.awt.event.FocusEvent evt) {
|
||||
txtCommonPathFocusLost(evt);
|
||||
}
|
||||
});
|
||||
|
||||
btnSetCommonPath.setText(C.i18n("ui.button.explore")); // NOI18N
|
||||
btnSetCommonPath.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnSetCommonPathActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
btnCheckUpdate.setText(C.i18n("launcher.update_launcher")); // NOI18N
|
||||
btnCheckUpdate.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnCheckUpdateActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
btnMCBBS.setText("MCBBS");
|
||||
btnMCBBS.setToolTipText("");
|
||||
btnMCBBS.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnMCBBSActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
cboLang.setModel(new DefaultComboBoxModel(new String[]{C.i18n("color.blue"),C.i18n("color.green"),C.i18n("color.purple"),C.i18n("color.dark_blue"),C.i18n("color.orange"),C.i18n("color.red")}));
|
||||
cboLang.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboLangItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblLang.setText(C.i18n("launcher.lang")); // NOI18N
|
||||
|
||||
cboProxyType.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Direct", "HTTP", "Socks" }));
|
||||
cboProxyType.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboProxyTypeItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout pnlGeneralLayout = new javax.swing.GroupLayout(pnlGeneral);
|
||||
pnlGeneral.setLayout(pnlGeneralLayout);
|
||||
pnlGeneralLayout.setHorizontalGroup(
|
||||
pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlGeneralLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlGeneralLayout.createSequentialGroup()
|
||||
.addComponent(btnCheckUpdate)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnMCBBS)
|
||||
.addGap(0, 0, Short.MAX_VALUE))
|
||||
.addGroup(pnlGeneralLayout.createSequentialGroup()
|
||||
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(lblDownloadSource)
|
||||
.addComponent(lblCommonPath)
|
||||
.addComponent(lblLang)
|
||||
.addComponent(lblProxy))
|
||||
.addGap(28, 28, 28)
|
||||
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlGeneralLayout.createSequentialGroup()
|
||||
.addComponent(cboProxyType, 0, 156, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblProxyHost)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(txtProxyHost, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblProxyPort)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(txtProxyPort, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblProxyUserName)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(txtProxyUsername, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblProxyPassword)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(txtProxyPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addComponent(cboLang, javax.swing.GroupLayout.Alignment.TRAILING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(pnlGeneralLayout.createSequentialGroup()
|
||||
.addComponent(txtCommonPath)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnSetCommonPath))
|
||||
.addComponent(cboDownloadSource, javax.swing.GroupLayout.Alignment.TRAILING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
|
||||
.addContainerGap())
|
||||
);
|
||||
pnlGeneralLayout.setVerticalGroup(
|
||||
pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlGeneralLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblCommonPath)
|
||||
.addComponent(txtCommonPath, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(btnSetCommonPath, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblDownloadSource)
|
||||
.addComponent(cboDownloadSource, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(cboLang, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblLang))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(txtProxyHost, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblProxy)
|
||||
.addComponent(lblProxyHost)
|
||||
.addComponent(txtProxyPort, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblProxyPort)
|
||||
.addComponent(txtProxyUsername, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblProxyUserName)
|
||||
.addComponent(txtProxyPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblProxyPassword)
|
||||
.addComponent(cboProxyType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 69, Short.MAX_VALUE)
|
||||
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(btnCheckUpdate, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(btnMCBBS, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
tabLauncherSettings.addTab(C.i18n("launcher.tab.general"), pnlGeneral); // NOI18N
|
||||
|
||||
lblTheme.setText(C.i18n("launcher.theme")); // NOI18N
|
||||
|
||||
cboTheme.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboThemeItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblBackground.setText(C.i18n("launcher.background_location")); // NOI18N
|
||||
lblBackground.setToolTipText("");
|
||||
|
||||
txtBackgroundPath.setToolTipText(C.i18n("launcher.background_tooltip")); // NOI18N
|
||||
txtBackgroundPath.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusLost(java.awt.event.FocusEvent evt) {
|
||||
txtBackgroundPathFocusLost(evt);
|
||||
}
|
||||
});
|
||||
|
||||
chkEnableShadow.setText(C.i18n("launcher.enable_shadow")); // NOI18N
|
||||
chkEnableShadow.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
chkEnableShadowItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
chkEnableBlur.setText(C.i18n("launcher.enable_blur")); // NOI18N
|
||||
chkEnableBlur.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
chkEnableBlurItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
chkEnableAnimation.setText(C.i18n("launcher.enable_animation")); // NOI18N
|
||||
chkEnableAnimation.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
chkEnableAnimationItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
chkDecorated.setText(C.i18n("launcher.decorated")); // NOI18N
|
||||
chkDecorated.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
chkDecoratedItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
btnSelBackgroundPath.setText(C.i18n("ui.button.explore")); // NOI18N
|
||||
btnSelBackgroundPath.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnSelBackgroundPathActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
cboFontFamily.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboFontFamilyItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblFont.setText(C.i18n("launcher.log_font")); // NOI18N
|
||||
|
||||
spinnerFontSize.addChangeListener(new javax.swing.event.ChangeListener() {
|
||||
public void stateChanged(javax.swing.event.ChangeEvent evt) {
|
||||
spinnerFontSizeStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblExample.setText("19:10:10 [Client Thread/INFO]: Example");
|
||||
|
||||
javax.swing.GroupLayout pnlUILayout = new javax.swing.GroupLayout(pnlUI);
|
||||
pnlUI.setLayout(pnlUILayout);
|
||||
pnlUILayout.setHorizontalGroup(
|
||||
pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlUILayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlUILayout.createSequentialGroup()
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(chkDecorated)
|
||||
.addGroup(pnlUILayout.createSequentialGroup()
|
||||
.addComponent(chkEnableShadow)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(chkEnableBlur)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(chkEnableAnimation)))
|
||||
.addGap(0, 163, Short.MAX_VALUE))
|
||||
.addGroup(pnlUILayout.createSequentialGroup()
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(lblBackground)
|
||||
.addComponent(lblFont)
|
||||
.addComponent(lblTheme))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(cboTheme, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(pnlUILayout.createSequentialGroup()
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(txtBackgroundPath)
|
||||
.addComponent(cboFontFamily, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
|
||||
.addComponent(btnSelBackgroundPath, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(spinnerFontSize)))
|
||||
.addGroup(pnlUILayout.createSequentialGroup()
|
||||
.addComponent(lblExample)
|
||||
.addGap(0, 0, Short.MAX_VALUE)))))
|
||||
.addContainerGap())
|
||||
);
|
||||
pnlUILayout.setVerticalGroup(
|
||||
pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlUILayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblBackground)
|
||||
.addComponent(txtBackgroundPath, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(btnSelBackgroundPath, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(cboFontFamily, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblFont)
|
||||
.addComponent(spinnerFontSize, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblExample)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(cboTheme, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblTheme))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 63, Short.MAX_VALUE)
|
||||
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(chkEnableShadow)
|
||||
.addComponent(chkEnableBlur)
|
||||
.addComponent(chkEnableAnimation))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(chkDecorated)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
tabLauncherSettings.addTab(C.i18n("launcher.tab.ui"), pnlUI); // NOI18N
|
||||
|
||||
lblAbout.setText(C.i18n("launcher.about")); // NOI18N
|
||||
lblAbout.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
|
||||
|
||||
javax.swing.GroupLayout pnlAboutLayout = new javax.swing.GroupLayout(pnlAbout);
|
||||
pnlAbout.setLayout(pnlAboutLayout);
|
||||
pnlAboutLayout.setHorizontalGroup(
|
||||
pnlAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlAboutLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(lblAbout, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap(252, Short.MAX_VALUE))
|
||||
);
|
||||
pnlAboutLayout.setVerticalGroup(
|
||||
pnlAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlAboutLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addComponent(lblAbout, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
);
|
||||
|
||||
tabLauncherSettings.addTab(C.i18n("launcher.tab.about"), pnlAbout); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
|
||||
this.setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addGap(10, 10, 10)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(lblModpack, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblRestart))
|
||||
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addComponent(tabLauncherSettings)
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(tabLauncherSettings)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblModpack, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblRestart)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
tabLauncherSettings.getAccessibleContext().setAccessibleName("");
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void cboDownloadSourceItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboDownloadSourceItemStateChanged
|
||||
Settings.getInstance().setDownloadType(cboDownloadSource.getSelectedIndex());
|
||||
}//GEN-LAST:event_cboDownloadSourceItemStateChanged
|
||||
|
||||
private void btnSelBackgroundPathActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSelBackgroundPathActionPerformed
|
||||
JSystemFileChooser fc = new JSystemFileChooser();
|
||||
fc.setFileSelectionMode(JSystemFileChooser.FILES_ONLY);
|
||||
fc.setDialogTitle(C.i18n("launcher.choose_bgpath"));
|
||||
fc.setMultiSelectionEnabled(false);
|
||||
fc.setFileFilter(new FileNameExtensionFilter("*.png", "png"));
|
||||
fc.addChoosableFileFilter(new FileNameExtensionFilter("*.jpg", "jpg"));
|
||||
if (fc.showOpenDialog(this) != JSystemFileChooser.APPROVE_OPTION || fc.getSelectedFile() == null)
|
||||
return;
|
||||
try {
|
||||
String path = fc.getSelectedFile().getCanonicalPath();
|
||||
path = IOUtils.removeLastSeparator(path);
|
||||
txtBackgroundPath.setText(path);
|
||||
Settings.getInstance().setBgpath(path);
|
||||
MainFrame.INSTANCE.loadBackground();
|
||||
} catch (IOException e) {
|
||||
HMCLog.warn("Failed to set background path.", e);
|
||||
MessageBox.show(C.i18n("ui.label.failed_set") + e.getMessage());
|
||||
}
|
||||
}//GEN-LAST:event_btnSelBackgroundPathActionPerformed
|
||||
|
||||
private void txtBackgroundPathFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtBackgroundPathFocusLost
|
||||
Settings.getInstance().setBgpath(txtBackgroundPath.getText());
|
||||
MainFrame.INSTANCE.loadBackground();
|
||||
}//GEN-LAST:event_txtBackgroundPathFocusLost
|
||||
|
||||
private void btnCheckUpdateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCheckUpdateActionPerformed
|
||||
Settings.UPDATE_CHECKER.process(true);
|
||||
Settings.UPDATE_CHECKER.checkOutdate();
|
||||
}//GEN-LAST:event_btnCheckUpdateActionPerformed
|
||||
|
||||
private void cboThemeItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboThemeItemStateChanged
|
||||
Settings.getInstance().setTheme(((Theme) cboTheme.getSelectedItem()).id);
|
||||
}//GEN-LAST:event_cboThemeItemStateChanged
|
||||
|
||||
private void lblModpackMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_lblModpackMouseClicked
|
||||
SwingUtils.openLink("http://huangyuhui.duapp.com/link.php?type=modpack");
|
||||
}//GEN-LAST:event_lblModpackMouseClicked
|
||||
|
||||
private void cboLangItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboLangItemStateChanged
|
||||
Settings.getInstance().setLocalization(SupportedLocales.values()[cboLang.getSelectedIndex()].name());
|
||||
}//GEN-LAST:event_cboLangItemStateChanged
|
||||
|
||||
private void chkEnableShadowItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkEnableShadowItemStateChanged
|
||||
Settings.getInstance().setEnableShadow(chkEnableShadow.isSelected());
|
||||
}//GEN-LAST:event_chkEnableShadowItemStateChanged
|
||||
|
||||
private void chkDecoratedItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkDecoratedItemStateChanged
|
||||
Settings.getInstance().setDecorated(chkDecorated.isSelected());
|
||||
}//GEN-LAST:event_chkDecoratedItemStateChanged
|
||||
|
||||
private void txtProxyHostFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtProxyHostFocusLost
|
||||
Settings.getInstance().setProxyHost(txtProxyHost.getText());
|
||||
}//GEN-LAST:event_txtProxyHostFocusLost
|
||||
|
||||
private void txtProxyPortFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtProxyPortFocusLost
|
||||
Settings.getInstance().setProxyPort(txtProxyPort.getText());
|
||||
}//GEN-LAST:event_txtProxyPortFocusLost
|
||||
|
||||
private void txtProxyUsernameFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtProxyUsernameFocusLost
|
||||
Settings.getInstance().setProxyUserName(txtProxyUsername.getText());
|
||||
}//GEN-LAST:event_txtProxyUsernameFocusLost
|
||||
|
||||
private void txtProxyPasswordFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtProxyPasswordFocusLost
|
||||
Settings.getInstance().setProxyPassword(txtProxyPassword.getText());
|
||||
}//GEN-LAST:event_txtProxyPasswordFocusLost
|
||||
|
||||
private void btnMCBBSActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnMCBBSActionPerformed
|
||||
SwingUtils.openLink(C.URL_PUBLISH);
|
||||
}//GEN-LAST:event_btnMCBBSActionPerformed
|
||||
|
||||
private void chkEnableBlurItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkEnableBlurItemStateChanged
|
||||
Settings.getInstance().setEnableBlur(chkEnableBlur.isSelected());
|
||||
}//GEN-LAST:event_chkEnableBlurItemStateChanged
|
||||
|
||||
private void chkEnableAnimationItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkEnableAnimationItemStateChanged
|
||||
Settings.getInstance().setEnableAnimation(chkEnableAnimation.isSelected());
|
||||
}//GEN-LAST:event_chkEnableAnimationItemStateChanged
|
||||
|
||||
private void btnSetCommonPathActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSetCommonPathActionPerformed
|
||||
JSystemFileChooser fc = new JSystemFileChooser();
|
||||
fc.setFileSelectionMode(JSystemFileChooser.DIRECTORIES_ONLY);
|
||||
fc.setDialogTitle(C.i18n("launcher.choose_commonpath"));
|
||||
fc.setMultiSelectionEnabled(false);
|
||||
if (fc.showOpenDialog(this) != JSystemFileChooser.APPROVE_OPTION || fc.getSelectedFile() == null)
|
||||
return;
|
||||
try {
|
||||
String path = fc.getSelectedFile().getCanonicalPath();
|
||||
txtCommonPath.setText(path);
|
||||
Settings.getInstance().setCommonpath(path);
|
||||
} catch (IOException e) {
|
||||
HMCLog.warn("Failed to set common path.", e);
|
||||
MessageBox.show(C.i18n("ui.label.failed_set") + e.getMessage());
|
||||
}
|
||||
}//GEN-LAST:event_btnSetCommonPathActionPerformed
|
||||
|
||||
private void txtCommonPathFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtCommonPathFocusLost
|
||||
Settings.getInstance().setCommonpath(txtCommonPath.getText());
|
||||
}//GEN-LAST:event_txtCommonPathFocusLost
|
||||
|
||||
private void cboFontFamilyItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboFontFamilyItemStateChanged
|
||||
Settings.getInstance().setFontFamily(evt.getItem().toString());
|
||||
lblExample.setFont(Settings.getInstance().getConsoleFont());
|
||||
}//GEN-LAST:event_cboFontFamilyItemStateChanged
|
||||
|
||||
private void spinnerFontSizeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerFontSizeStateChanged
|
||||
Settings.getInstance().setFontSize(MathUtils.parseInt(spinnerFontSize.getValue().toString(), 12));
|
||||
lblExample.setFont(Settings.getInstance().getConsoleFont());
|
||||
}//GEN-LAST:event_spinnerFontSizeStateChanged
|
||||
|
||||
private void cboProxyTypeItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboProxyTypeItemStateChanged
|
||||
Settings.getInstance().setProxyType(Proxy.Type.values()[cboProxyType.getSelectedIndex()]);
|
||||
}//GEN-LAST:event_cboProxyTypeItemStateChanged
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton btnCheckUpdate;
|
||||
private javax.swing.JButton btnMCBBS;
|
||||
private javax.swing.JButton btnSelBackgroundPath;
|
||||
private javax.swing.JButton btnSetCommonPath;
|
||||
private javax.swing.JComboBox cboDownloadSource;
|
||||
private javax.swing.JComboBox cboFontFamily;
|
||||
private javax.swing.JComboBox cboLang;
|
||||
private javax.swing.JComboBox<String> cboProxyType;
|
||||
private javax.swing.JComboBox cboTheme;
|
||||
private javax.swing.JCheckBox chkDecorated;
|
||||
private javax.swing.JCheckBox chkEnableAnimation;
|
||||
private javax.swing.JCheckBox chkEnableBlur;
|
||||
private javax.swing.JCheckBox chkEnableShadow;
|
||||
private javax.swing.JLabel lblAbout;
|
||||
private javax.swing.JLabel lblBackground;
|
||||
private javax.swing.JLabel lblCommonPath;
|
||||
private javax.swing.JLabel lblDownloadSource;
|
||||
private javax.swing.JLabel lblExample;
|
||||
private javax.swing.JLabel lblFont;
|
||||
private javax.swing.JLabel lblLang;
|
||||
private javax.swing.JLabel lblModpack;
|
||||
private javax.swing.JLabel lblProxy;
|
||||
private javax.swing.JLabel lblProxyHost;
|
||||
private javax.swing.JLabel lblProxyPassword;
|
||||
private javax.swing.JLabel lblProxyPort;
|
||||
private javax.swing.JLabel lblProxyUserName;
|
||||
private javax.swing.JLabel lblRestart;
|
||||
private javax.swing.JLabel lblTheme;
|
||||
private javax.swing.JPanel pnlAbout;
|
||||
private javax.swing.JPanel pnlGeneral;
|
||||
private javax.swing.JPanel pnlUI;
|
||||
private javax.swing.JSpinner spinnerFontSize;
|
||||
private javax.swing.JTabbedPane tabLauncherSettings;
|
||||
private javax.swing.JTextField txtBackgroundPath;
|
||||
private javax.swing.JTextField txtCommonPath;
|
||||
private javax.swing.JTextField txtProxyHost;
|
||||
private javax.swing.JTextField txtProxyPassword;
|
||||
private javax.swing.JTextField txtProxyPort;
|
||||
private javax.swing.JTextField txtProxyUsername;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PipedOutputStream;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.api.event.process.JVMLaunchFailedEvent;
|
||||
import org.jackhuang.hmcl.api.event.process.JavaProcessExitedAbnormallyEvent;
|
||||
import org.jackhuang.hmcl.api.event.process.JavaProcessStoppedEvent;
|
||||
import org.jackhuang.hmcl.api.event.launch.LaunchEvent;
|
||||
import org.jackhuang.hmcl.api.event.launch.LaunchSucceededEvent;
|
||||
import org.jackhuang.hmcl.api.event.launch.LaunchingStateChangedEvent;
|
||||
import org.jackhuang.hmcl.util.LauncherVisibility;
|
||||
import org.jackhuang.hmcl.core.launch.GameLauncher;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.util.HMCLGameLauncher;
|
||||
import org.jackhuang.hmcl.util.MinecraftCrashAdvicer;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
import org.jackhuang.hmcl.api.func.Consumer;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.api.event.launch.LaunchingState;
|
||||
import org.jackhuang.hmcl.util.DefaultPlugin;
|
||||
import org.jackhuang.hmcl.util.Log4jHandler;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.util.sys.FileUtils;
|
||||
import org.jackhuang.hmcl.util.sys.PrintlnEvent;
|
||||
import org.jackhuang.hmcl.util.sys.ProcessMonitor;
|
||||
import org.xml.sax.XMLReader;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class LaunchingUIDaemon {
|
||||
|
||||
XMLReader reader;
|
||||
Log4jHandler logHandler;
|
||||
|
||||
public LaunchingUIDaemon() {
|
||||
HMCLApi.EVENT_BUS.channel(LaunchingStateChangedEvent.class).register(LAUNCHING_STATE_CHANGED);
|
||||
HMCLApi.EVENT_BUS.channel(LaunchEvent.class).register(p -> {
|
||||
GameLauncher obj = (GameLauncher) p.getSource();
|
||||
HMCLGameLauncher.GameLauncherTag tag = (HMCLGameLauncher.GameLauncherTag) obj.getTag();
|
||||
if (tag.launcherVisibility == LauncherVisibility.CLOSE && !LogWindow.INSTANCE.isVisible()) {
|
||||
HMCLog.log("Without the option of keeping the launcher visible, this application will exit and will NOT catch game logs, but you can turn on \"Debug Mode\".");
|
||||
System.exit(0);
|
||||
} else if (tag.launcherVisibility == LauncherVisibility.KEEP)
|
||||
MainFrame.INSTANCE.closeMessage();
|
||||
else {
|
||||
if (LogWindow.INSTANCE.isVisible())
|
||||
LogWindow.INSTANCE.setExit(() -> true);
|
||||
HMCLApi.EVENT_BUS.fireChannel(new LaunchingStateChangedEvent(obj, LaunchingState.WaitingForGameLaunching));
|
||||
}
|
||||
// We promise that JavaProcessMonitor.tag is LauncherVisibility
|
||||
// See events below.
|
||||
ProcessMonitor monitor = new ProcessMonitor(p.getValue());
|
||||
PipedOutputStream os = new PipedOutputStream();
|
||||
monitor.setTag(obj);
|
||||
try {
|
||||
logHandler = new Log4jHandler(monitor, os, false);
|
||||
logHandler.addForbiddenToken(obj.getLoginResult().getAccessToken(), "<access token>");
|
||||
logHandler.addForbiddenToken(obj.getLoginResult().getSession(), "<session>");
|
||||
logHandler.addForbiddenToken(obj.getLoginResult().getUserId(), "<uuid>");
|
||||
logHandler.addForbiddenToken("Setting user: " + obj.getLoginResult().getUserName(), "<player>");
|
||||
logHandler.start();
|
||||
} catch(Exception e) {
|
||||
HMCLog.err("", e);
|
||||
}
|
||||
monitor.registerPrintlnEvent(new PrintlnProcessor(obj, os));
|
||||
monitor.start();
|
||||
});
|
||||
HMCLApi.EVENT_BUS.channel(LaunchSucceededEvent.class).register(p -> {
|
||||
int state = ((HMCLGameLauncher.GameLauncherTag) ((GameLauncher) p.getSource()).getTag()).state;
|
||||
if (state == 1)
|
||||
LAUNCH_FINISHER.accept(p);
|
||||
else if (state == 2)
|
||||
LAUNCH_SCRIPT_FINISHER.accept(p);
|
||||
});
|
||||
HMCLApi.EVENT_BUS.channel(JavaProcessStoppedEvent.class).register(event -> {
|
||||
GameLauncher launcher = ((GameLauncher) ((ProcessMonitor) event.getSource()).getTag());
|
||||
HMCLApi.EVENT_BUS.fireChannel(new LaunchingStateChangedEvent(launcher, LaunchingState.Done));
|
||||
checkExit(unpackProcessMonitor(event.getSource()));
|
||||
});
|
||||
HMCLApi.EVENT_BUS.channel(JavaProcessExitedAbnormallyEvent.class).register(event -> {
|
||||
ProcessMonitor monitor = (ProcessMonitor) event.getSource();
|
||||
int exitCode = event.getValue().getExitCode();
|
||||
HMCLog.err("The game exited abnormally, exit code: " + exitCode);
|
||||
monitor.waitForCommandLineCompletion();
|
||||
String[] logs = event.getValue().getStdOutLines().toArray(new String[0]);
|
||||
String errorText = null;
|
||||
for (String s : logs) {
|
||||
int pos = s.lastIndexOf("#@!@#");
|
||||
if (pos >= 0 && pos < s.length() - "#@!@#".length() - 1) {
|
||||
errorText = s.substring(pos + "#@!@#".length()).trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
String msg = C.i18n("launch.exited_abnormally") + " exit code: " + exitCode;
|
||||
if (errorText != null)
|
||||
msg += ", advice: " + MinecraftCrashAdvicer.getAdvice(FileUtils.readQuietly(new File(errorText)));
|
||||
HMCLog.err(msg);
|
||||
SwingUtilities.invokeLater(() -> LogWindow.INSTANCE.setVisible(true));
|
||||
noExitThisTime = true;
|
||||
});
|
||||
HMCLApi.EVENT_BUS.channel(JVMLaunchFailedEvent.class).register(event -> {
|
||||
int exitCode = event.getValue().getExitCode();
|
||||
HMCLog.err("Cannot create jvm, exit code: " + exitCode);
|
||||
SwingUtilities.invokeLater(() -> LogWindow.INSTANCE.setVisible(true));
|
||||
noExitThisTime = true;
|
||||
});
|
||||
}
|
||||
|
||||
static LauncherVisibility unpackProcessMonitor(Object obj) {
|
||||
GameLauncher launcher = ((GameLauncher) ((ProcessMonitor) obj).getTag());
|
||||
HMCLGameLauncher.GameLauncherTag tag = (HMCLGameLauncher.GameLauncherTag) launcher.getTag();
|
||||
return tag.launcherVisibility;
|
||||
}
|
||||
|
||||
boolean noExitThisTime = false;
|
||||
|
||||
void runGame(Profile profile) {
|
||||
MainFrame.INSTANCE.showMessage(C.i18n("ui.message.launching"));
|
||||
profile.launcher().genLaunchCode(value -> {
|
||||
DefaultPlugin.INSTANCE.saveAuthenticatorConfig();
|
||||
((HMCLGameLauncher.GameLauncherTag) value.getTag()).state = 1;
|
||||
}, MainFrame.INSTANCE::failed, Settings.getInstance().getAuthenticator().getPassword());
|
||||
}
|
||||
|
||||
void testGame(Profile profile) {
|
||||
MainFrame.INSTANCE.showMessage(C.i18n("ui.message.launching"));
|
||||
profile.launcher().genLaunchCode(value -> {
|
||||
DefaultPlugin.INSTANCE.saveAuthenticatorConfig();
|
||||
((HMCLGameLauncher.GameLauncherTag) value.getTag()).state = 1;
|
||||
((HMCLGameLauncher.GameLauncherTag) value.getTag()).launcherVisibility = LauncherVisibility.KEEP;
|
||||
}, MainFrame.INSTANCE::failed, Settings.getInstance().getAuthenticator().getPassword());
|
||||
}
|
||||
|
||||
void makeLaunchScript(Profile profile) {
|
||||
MainFrame.INSTANCE.showMessage(C.i18n("ui.message.launching"));
|
||||
profile.launcher().genLaunchCode(value -> {
|
||||
DefaultPlugin.INSTANCE.saveAuthenticatorConfig();
|
||||
((HMCLGameLauncher.GameLauncherTag) value.getTag()).state = 2;
|
||||
}, MainFrame.INSTANCE::failed, Settings.getInstance().getAuthenticator().getPassword());
|
||||
}
|
||||
|
||||
class PrintlnProcessor implements Consumer<PrintlnEvent> {
|
||||
|
||||
GameLauncher launcher;
|
||||
PipedOutputStream os;
|
||||
|
||||
public PrintlnProcessor(GameLauncher launcher, PipedOutputStream os) {
|
||||
this.launcher = launcher;
|
||||
this.os = os;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(PrintlnEvent t) {
|
||||
String log = t.getLine();
|
||||
if (!t.isError() && logHandler != null) {
|
||||
logHandler.newLogLine(log + C.LINE_SEPARATOR);
|
||||
}
|
||||
else System.err.println(t.getLine());
|
||||
HMCLGameLauncher.GameLauncherTag tag = (HMCLGameLauncher.GameLauncherTag) launcher.getTag();
|
||||
LauncherVisibility l = tag.launcherVisibility;
|
||||
if (t.getLine().contains("LWJGL Version: ") && l != LauncherVisibility.KEEP)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (l != LauncherVisibility.HIDE_AND_REOPEN)
|
||||
MainFrame.INSTANCE.dispose();
|
||||
else { // If current state is 'hide and reopen', closes the main window and reset the state to normal.
|
||||
MainFrame.INSTANCE.setVisible(false);
|
||||
HMCLApi.EVENT_BUS.fireChannel(new LaunchingStateChangedEvent(launcher, LaunchingState.Done));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Consumer<LaunchingStateChangedEvent> LAUNCHING_STATE_CHANGED = t -> {
|
||||
String message = null;
|
||||
switch (t.getValue()) {
|
||||
case LoggingIn:
|
||||
message = "launch.state.logging_in";
|
||||
break;
|
||||
case GeneratingLaunchingCodes:
|
||||
message = "launch.state.generating_launching_codes";
|
||||
break;
|
||||
case DownloadingLibraries:
|
||||
message = "launch.state.downloading_libraries";
|
||||
break;
|
||||
case DecompressingNatives:
|
||||
message = "launch.state.decompressing_natives";
|
||||
break;
|
||||
case WaitingForGameLaunching:
|
||||
message = "launch.state.waiting_launching";
|
||||
break;
|
||||
case Done:
|
||||
MainFrame.INSTANCE.closeMessage();
|
||||
return;
|
||||
}
|
||||
MainFrame.INSTANCE.showMessage(C.i18n(message));
|
||||
};
|
||||
|
||||
private static final Consumer<LaunchSucceededEvent> LAUNCH_FINISHER = event -> {
|
||||
try {
|
||||
((GameLauncher) event.getSource()).launch(event.getValue());
|
||||
} catch (IOException e) {
|
||||
MainFrame.INSTANCE.failed(C.i18n("launch.failed_creating_process") + "\n" + e.getMessage());
|
||||
HMCLog.err("Failed to launch when creating a new process.", e);
|
||||
}
|
||||
};
|
||||
|
||||
private void checkExit(LauncherVisibility v) {
|
||||
if (v == LauncherVisibility.HIDE_AND_REOPEN) {
|
||||
MainFrame.INSTANCE.setVisible(true);
|
||||
} else if (v != LauncherVisibility.KEEP && !LogWindow.INSTANCE.isVisible() && !noExitThisTime) {
|
||||
System.exit(0);
|
||||
} else {
|
||||
noExitThisTime = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Consumer<LaunchSucceededEvent> LAUNCH_SCRIPT_FINISHER = event -> {
|
||||
try {
|
||||
String s = MessageBox.showInputDialog(C.i18n("mainwindow.enter_script_name"));
|
||||
if (StrUtils.isNotBlank(s))
|
||||
MessageBox.show(C.i18n("mainwindow.make_launch_succeed") + " " + ((GameLauncher) event.getSource()).makeLauncher(s, event.getValue()).getAbsolutePath());
|
||||
} catch (IOException ex) {
|
||||
MessageBox.show(C.i18n("mainwindow.make_launch_script_failed"));
|
||||
HMCLog.err("Failed to create script file.", ex);
|
||||
}
|
||||
MainFrame.INSTANCE.closeMessage();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.event.EventBus;
|
||||
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.game.HMCLGameRepository;
|
||||
import org.jackhuang.hmcl.game.ModpackHelper;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.task.TaskExecutor;
|
||||
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.account.AddAccountPane;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.profile.ProfileAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.util.io.CompressingUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class LeftPaneController extends AdvancedListBox {
|
||||
|
||||
public LeftPaneController() {
|
||||
|
||||
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
|
||||
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
|
||||
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
|
||||
|
||||
GameAdvancedListItem gameListItem = new GameAdvancedListItem();
|
||||
gameListItem.actionButtonVisibleProperty().bind(Profiles.selectedVersionProperty().isNotNull());
|
||||
gameListItem.setOnAction(e -> {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
String version = Profiles.getSelectedVersion();
|
||||
if (version == null) {
|
||||
Controllers.navigate(Controllers.getGameListPage());
|
||||
} else {
|
||||
Versions.modifyGameSettings(profile, version);
|
||||
}
|
||||
});
|
||||
|
||||
ProfileAdvancedListItem profileListItem = new ProfileAdvancedListItem();
|
||||
profileListItem.setOnAction(e -> Controllers.navigate(Controllers.getProfileListPage()));
|
||||
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
|
||||
|
||||
AdvancedListItem gameItem = new AdvancedListItem();
|
||||
gameItem.setImage(new Image("/assets/img/bookshelf.png"));
|
||||
gameItem.setTitle(i18n("version.manage"));
|
||||
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
|
||||
|
||||
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
|
||||
launcherSettingsItem.setImage(new Image("/assets/img/command.png"));
|
||||
launcherSettingsItem.setTitle(i18n("settings.launcher"));
|
||||
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
|
||||
|
||||
this
|
||||
.startCategory(i18n("account").toUpperCase())
|
||||
.add(accountListItem)
|
||||
.startCategory(i18n("version").toUpperCase())
|
||||
.add(gameListItem)
|
||||
.add(gameItem)
|
||||
.startCategory(i18n("profile.title").toUpperCase())
|
||||
.add(profileListItem)
|
||||
.startCategory(i18n("launcher").toUpperCase())
|
||||
.add(launcherSettingsItem);
|
||||
|
||||
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
|
||||
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
if (profile != null && profile.getRepository().isLoaded())
|
||||
onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());
|
||||
}
|
||||
|
||||
// ==== Accounts ====
|
||||
public void checkAccount() {
|
||||
if (Accounts.getAccounts().isEmpty())
|
||||
Platform.runLater(this::addNewAccount);
|
||||
}
|
||||
|
||||
private void addNewAccount() {
|
||||
Controllers.dialog(new AddAccountPane());
|
||||
}
|
||||
// ====
|
||||
|
||||
private boolean checkedModpack = false;
|
||||
|
||||
private void onRefreshedVersions(HMCLGameRepository repository) {
|
||||
JFXUtilities.runInFX(() -> {
|
||||
if (!checkedModpack) {
|
||||
checkedModpack = true;
|
||||
|
||||
if (repository.getVersionCount() == 0) {
|
||||
File modpackFile = new File("modpack.zip").getAbsoluteFile();
|
||||
if (modpackFile.exists()) {
|
||||
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
|
||||
.thenApply(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
|
||||
.thenAccept(modpack -> {
|
||||
AtomicReference<Region> region = new AtomicReference<>();
|
||||
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
|
||||
.withRun(Schedulers.javafx(), this::checkAccount).executor();
|
||||
region.set(Controllers.taskDialog(executor, i18n("modpack.installing")));
|
||||
executor.start();
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkAccount();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
|
||||
public abstract class ListPage<T extends Node> extends Control {
|
||||
private final ListProperty<T> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
|
||||
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false);
|
||||
private final BooleanProperty refreshable = new SimpleBooleanProperty(this, "refreshable", false);
|
||||
|
||||
public abstract void add();
|
||||
|
||||
public void refresh() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new ListPageSkin(this);
|
||||
}
|
||||
|
||||
public ObservableList<T> getItems() {
|
||||
return items.get();
|
||||
}
|
||||
|
||||
public void setItems(ObservableList<T> items) {
|
||||
this.items.set(items);
|
||||
}
|
||||
|
||||
public ListProperty<T> itemsProperty() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public boolean isLoading() {
|
||||
return loading.get();
|
||||
}
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
this.loading.set(loading);
|
||||
}
|
||||
|
||||
public BooleanProperty loadingProperty() {
|
||||
return loading;
|
||||
}
|
||||
|
||||
public boolean isRefreshable() {
|
||||
return refreshable.get();
|
||||
}
|
||||
|
||||
public BooleanProperty refreshableProperty() {
|
||||
return refreshable;
|
||||
}
|
||||
|
||||
public void setRefreshable(boolean refreshable) {
|
||||
this.refreshable.set(refreshable);
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXScrollPane;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
|
||||
public class ListPageSkin extends SkinBase<ListPage<?>> {
|
||||
|
||||
public ListPageSkin(ListPage<?> skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
SpinnerPane spinnerPane = new SpinnerPane();
|
||||
spinnerPane.getStyleClass().add("large-spinner-pane");
|
||||
Pane placeholder = new Pane();
|
||||
|
||||
StackPane contentPane = new StackPane();
|
||||
{
|
||||
ScrollPane scrollPane = new ScrollPane();
|
||||
{
|
||||
scrollPane.setFitToWidth(true);
|
||||
|
||||
VBox list = new VBox();
|
||||
list.maxWidthProperty().bind(scrollPane.widthProperty());
|
||||
list.setSpacing(10);
|
||||
list.setPadding(new Insets(10));
|
||||
|
||||
VBox content = new VBox();
|
||||
content.getChildren().setAll(list, placeholder);
|
||||
|
||||
Bindings.bindContent(list.getChildren(), skinnable.itemsProperty());
|
||||
|
||||
scrollPane.setContent(content);
|
||||
JFXScrollPane.smoothScrolling(scrollPane);
|
||||
}
|
||||
|
||||
VBox vBox = new VBox();
|
||||
{
|
||||
vBox.setAlignment(Pos.BOTTOM_RIGHT);
|
||||
vBox.setPickOnBounds(false);
|
||||
vBox.setPadding(new Insets(15));
|
||||
vBox.setSpacing(15);
|
||||
|
||||
JFXButton btnAdd = new JFXButton();
|
||||
FXUtils.setLimitWidth(btnAdd, 40);
|
||||
FXUtils.setLimitHeight(btnAdd, 40);
|
||||
btnAdd.getStyleClass().add("jfx-button-raised-round");
|
||||
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnAdd.setGraphic(SVG.plus(Theme.whiteFillBinding(), -1, -1));
|
||||
btnAdd.setOnMouseClicked(e -> skinnable.add());
|
||||
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
FXUtils.setLimitWidth(btnRefresh, 40);
|
||||
FXUtils.setLimitHeight(btnRefresh, 40);
|
||||
btnRefresh.getStyleClass().add("jfx-button-raised-round");
|
||||
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.whiteFillBinding(), -1, -1));
|
||||
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
|
||||
|
||||
vBox.getChildren().setAll(btnAdd);
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.refreshableProperty(),
|
||||
refreshable -> {
|
||||
if (refreshable) vBox.getChildren().setAll(btnRefresh, btnAdd);
|
||||
else vBox.getChildren().setAll(btnAdd);
|
||||
});
|
||||
}
|
||||
|
||||
// Keep a blank space to prevent buttons from blocking up mod items.
|
||||
BorderPane group = new BorderPane();
|
||||
group.setPickOnBounds(false);
|
||||
group.setBottom(vBox);
|
||||
placeholder.minHeightProperty().bind(vBox.heightProperty());
|
||||
|
||||
contentPane.getChildren().setAll(scrollPane, group);
|
||||
}
|
||||
|
||||
spinnerPane.loadingProperty().bind(skinnable.loadingProperty());
|
||||
spinnerPane.setContent(contentPane);
|
||||
|
||||
getChildren().setAll(spinnerPane);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.3" maxVersion="1.8" type="org.netbeans.modules.form.forminfo.JFrameFormInfo">
|
||||
<Properties>
|
||||
<Property name="defaultCloseOperation" type="int" value="2"/>
|
||||
<Property name="title" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/I18N.properties" key="logwindow.title" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<SyntheticProperties>
|
||||
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
|
||||
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
|
||||
</SyntheticProperties>
|
||||
<Events>
|
||||
<EventHandler event="windowClosing" listener="java.awt.event.WindowListener" parameters="java.awt.event.WindowEvent" handler="formWindowClosing"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<Component id="btnContact" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="144" max="32767" attributes="0"/>
|
||||
<Component id="btnTerminateGame" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnCopy" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnClear" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnClose" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="lblCrash" alignment="0" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="lblShowLines" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="cboShowLines" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblSearch" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="txtSearch" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnFatal" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnError" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnWarn" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnInfo" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnDebug" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<Component id="pnlLog" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblShowLines" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="cboShowLines" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnDebug" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnInfo" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnWarn" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnError" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnFatal" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="txtSearch" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblSearch" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="lblCrash" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="pnlLog" pref="281" max="32767" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="btnClear" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnClose" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnCopy" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnContact" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnTerminateGame" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="btnClear">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/I18N.properties" key="ui.button.clear" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnClearActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnClose">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/I18N.properties" key="ui.button.close" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnCloseActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnCopy">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/I18N.properties" key="ui.button.copy" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnCopyActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblCrash">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/I18N.properties" key="ui.label.crashing" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnContact">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="logwindow.contact" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnContactActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnTerminateGame">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/I18N.properties" key="logwindow.terminate_game" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnTerminateGameActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Container class="javax.swing.JScrollPane" name="pnlLog">
|
||||
<AuxValues>
|
||||
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTextPane" name="txtLog">
|
||||
<Properties>
|
||||
<Property name="editable" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new JLineWrapTextPane()"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JLabel" name="lblShowLines">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="logwindow.show_lines" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" value=""/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboShowLines">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="3">
|
||||
<StringItem index="0" value="100"/>
|
||||
<StringItem index="1" value="1000"/>
|
||||
<StringItem index="2" value="5000"/>
|
||||
</StringArray>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboShowLinesItemStateChanged"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="<String>"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Component class="javax.swing.JToggleButton" name="btnDebug">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="0 debug"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JToggleButton" name="btnInfo">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="0 info"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JToggleButton" name="btnWarn">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="0 warn"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnError">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="0 error"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnFatal">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" value="0 fatal"/>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="txtSearch">
|
||||
<Properties>
|
||||
<Property name="toolTipText" type="java.lang.String" value=""/>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="txtSearchActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblSearch">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="logwindow.search" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
* Hello Minecraft!.
|
||||
* Copyright (C) 2013 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
|
||||
@@ -13,232 +13,413 @@
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyIntegerProperty;
|
||||
import javafx.beans.property.ReadOnlyIntegerWrapper;
|
||||
import javafx.concurrent.Worker;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.ComboBox;
|
||||
import javafx.scene.control.ToggleButton;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.web.WebEngine;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
import org.jackhuang.hmcl.event.Event;
|
||||
import org.jackhuang.hmcl.event.EventManager;
|
||||
import org.jackhuang.hmcl.game.LauncherHelper;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
import org.jackhuang.hmcl.util.Log4jLevel;
|
||||
import org.jackhuang.hmcl.util.StringUtils;
|
||||
import org.jackhuang.hmcl.util.io.IOUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.SimpleAttributeSet;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyledDocument;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.util.log.Level;
|
||||
import org.jackhuang.hmcl.api.func.NonFunction;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.util.DoubleOutputStream;
|
||||
import org.jackhuang.hmcl.util.Utils;
|
||||
import org.jackhuang.hmcl.util.sys.ProcessMonitor;
|
||||
import org.jackhuang.hmcl.util.ui.SwingUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class LogWindow extends Stage {
|
||||
public class LogWindow extends javax.swing.JFrame {
|
||||
|
||||
private final ReadOnlyIntegerWrapper fatal = new ReadOnlyIntegerWrapper(0);
|
||||
private final ReadOnlyIntegerWrapper error = new ReadOnlyIntegerWrapper(0);
|
||||
private final ReadOnlyIntegerWrapper warn = new ReadOnlyIntegerWrapper(0);
|
||||
private final ReadOnlyIntegerWrapper info = new ReadOnlyIntegerWrapper(0);
|
||||
private final ReadOnlyIntegerWrapper debug = new ReadOnlyIntegerWrapper(0);
|
||||
private final LogWindowImpl impl = new LogWindowImpl();
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
public final EventManager<Event> onDone = new EventManager<>();
|
||||
public static LogWindowOutputStream outputStream;
|
||||
|
||||
NonFunction<Boolean> listener;
|
||||
|
||||
/**
|
||||
* Creates new form LogWindow
|
||||
*/
|
||||
public LogWindow() {
|
||||
setScene(new Scene(impl, 800, 480));
|
||||
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
|
||||
setTitle(i18n("logwindow.title"));
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
initComponents();
|
||||
|
||||
DoubleOutputStream out = new DoubleOutputStream(outputStream = new LogWindowOutputStream(this, Level.INFO), System.out);
|
||||
System.setOut(new PrintStream(out));
|
||||
DoubleOutputStream err = new DoubleOutputStream(new LogWindowOutputStream(this, Level.ERROR), System.err);
|
||||
System.setErr(new PrintStream(err));
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
setLocationRelativeTo(null);
|
||||
//txtLog.setEditable(false);
|
||||
txtLog.setFont(Settings.getInstance().getConsoleFont());
|
||||
cboShowLines.setSelectedItem(String.valueOf(Settings.getInstance().getLogLines()));
|
||||
});
|
||||
}
|
||||
|
||||
public LogWindow(String text) {
|
||||
this();
|
||||
public static final LogWindow INSTANCE = new LogWindow();
|
||||
|
||||
onDone.register(() -> logLine(text, Log4jLevel.INFO));
|
||||
}
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
public ReadOnlyIntegerProperty fatalProperty() {
|
||||
return fatal.getReadOnlyProperty();
|
||||
}
|
||||
btnClear = new javax.swing.JButton();
|
||||
btnClose = new javax.swing.JButton();
|
||||
btnCopy = new javax.swing.JButton();
|
||||
lblCrash = new javax.swing.JLabel();
|
||||
btnContact = new javax.swing.JButton();
|
||||
btnTerminateGame = new javax.swing.JButton();
|
||||
pnlLog = new javax.swing.JScrollPane();
|
||||
txtLog = new JLineWrapTextPane();
|
||||
lblShowLines = new javax.swing.JLabel();
|
||||
cboShowLines = new javax.swing.JComboBox<>();
|
||||
btnDebug = new javax.swing.JToggleButton();
|
||||
btnInfo = new javax.swing.JToggleButton();
|
||||
btnWarn = new javax.swing.JToggleButton();
|
||||
btnError = new javax.swing.JButton();
|
||||
btnFatal = new javax.swing.JButton();
|
||||
txtSearch = new javax.swing.JTextField();
|
||||
lblSearch = new javax.swing.JLabel();
|
||||
|
||||
public int getFatal() {
|
||||
return fatal.get();
|
||||
}
|
||||
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setTitle(C.i18n("logwindow.title")); // NOI18N
|
||||
addWindowListener(new java.awt.event.WindowAdapter() {
|
||||
public void windowClosing(java.awt.event.WindowEvent evt) {
|
||||
formWindowClosing(evt);
|
||||
}
|
||||
});
|
||||
|
||||
public ReadOnlyIntegerProperty errorProperty() {
|
||||
return error.getReadOnlyProperty();
|
||||
}
|
||||
btnClear.setText(C.i18n("ui.button.clear")); // NOI18N
|
||||
btnClear.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnClearActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
public int getError() {
|
||||
return error.get();
|
||||
}
|
||||
btnClose.setText(C.i18n("ui.button.close")); // NOI18N
|
||||
btnClose.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnCloseActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
public ReadOnlyIntegerProperty warnProperty() {
|
||||
return warn.getReadOnlyProperty();
|
||||
}
|
||||
btnCopy.setText(C.i18n("ui.button.copy")); // NOI18N
|
||||
btnCopy.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnCopyActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
public int getWarn() {
|
||||
return warn.get();
|
||||
}
|
||||
lblCrash.setText(C.i18n("ui.label.crashing")); // NOI18N
|
||||
|
||||
public ReadOnlyIntegerProperty infoProperty() {
|
||||
return info.getReadOnlyProperty();
|
||||
}
|
||||
btnContact.setText(C.i18n("logwindow.contact")); // NOI18N
|
||||
btnContact.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnContactActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
public int getInfo() {
|
||||
return info.get();
|
||||
}
|
||||
btnTerminateGame.setText(C.i18n("logwindow.terminate_game")); // NOI18N
|
||||
btnTerminateGame.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnTerminateGameActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
public ReadOnlyIntegerProperty debugProperty() {
|
||||
return debug.getReadOnlyProperty();
|
||||
}
|
||||
txtLog.setEditable(false);
|
||||
pnlLog.setViewportView(txtLog);
|
||||
|
||||
public int getDebug() {
|
||||
return debug.get();
|
||||
}
|
||||
lblShowLines.setText(C.i18n("logwindow.show_lines")); // NOI18N
|
||||
lblShowLines.setToolTipText("");
|
||||
|
||||
public void logLine(String line, Log4jLevel level) {
|
||||
Element div = impl.document.createElement("div");
|
||||
// a <pre> element to prevent multiple spaces and tabs being removed.
|
||||
Element pre = impl.document.createElement("pre");
|
||||
pre.setTextContent(line);
|
||||
div.appendChild(pre);
|
||||
impl.body.appendChild(div);
|
||||
impl.engine.executeScript("checkNewLog(\"" + level.name().toLowerCase() + "\");scrollToBottom();");
|
||||
cboShowLines.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "100", "1000", "5000" }));
|
||||
cboShowLines.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboShowLinesItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
switch (level) {
|
||||
case FATAL:
|
||||
fatal.set(fatal.get() + 1);
|
||||
break;
|
||||
case ERROR:
|
||||
error.set(error.get() + 1);
|
||||
break;
|
||||
case WARN:
|
||||
warn.set(warn.get() + 1);
|
||||
break;
|
||||
case INFO:
|
||||
info.set(info.get() + 1);
|
||||
break;
|
||||
case DEBUG:
|
||||
debug.set(debug.get() + 1);
|
||||
break;
|
||||
default:
|
||||
// ignore
|
||||
break;
|
||||
btnDebug.setText("0 debug");
|
||||
|
||||
btnInfo.setText("0 info");
|
||||
|
||||
btnWarn.setText("0 warn");
|
||||
|
||||
btnError.setText("0 error");
|
||||
|
||||
btnFatal.setText("0 fatal");
|
||||
|
||||
txtSearch.setToolTipText("");
|
||||
txtSearch.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
txtSearchActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblSearch.setText(C.i18n("logwindow.search")); // NOI18N
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
|
||||
getContentPane().setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addComponent(btnContact)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 144, Short.MAX_VALUE)
|
||||
.addComponent(btnTerminateGame)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnCopy)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnClear)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnClose))
|
||||
.addComponent(lblCrash, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(lblShowLines)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(cboShowLines, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblSearch)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(txtSearch)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnFatal)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnError)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnWarn)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnInfo)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnDebug))
|
||||
.addComponent(pnlLog))
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblShowLines)
|
||||
.addComponent(cboShowLines, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(btnDebug)
|
||||
.addComponent(btnInfo)
|
||||
.addComponent(btnWarn)
|
||||
.addComponent(btnError)
|
||||
.addComponent(btnFatal)
|
||||
.addComponent(txtSearch, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblSearch))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(lblCrash)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(pnlLog, javax.swing.GroupLayout.DEFAULT_SIZE, 281, Short.MAX_VALUE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(btnClear)
|
||||
.addComponent(btnClose)
|
||||
.addComponent(btnCopy)
|
||||
.addComponent(btnContact)
|
||||
.addComponent(btnTerminateGame))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
pack();
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void btnCloseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCloseActionPerformed
|
||||
if (listener != null && listener.apply())
|
||||
terminateGames();
|
||||
SwingUtils.exitIfNoWindow(this, true);
|
||||
}//GEN-LAST:event_btnCloseActionPerformed
|
||||
|
||||
private void btnClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnClearActionPerformed
|
||||
clean();
|
||||
}//GEN-LAST:event_btnClearActionPerformed
|
||||
|
||||
private void btnCopyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCopyActionPerformed
|
||||
Utils.setClipboard(this.txtLog.getText());
|
||||
}//GEN-LAST:event_btnCopyActionPerformed
|
||||
|
||||
private void btnContactActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnContactActionPerformed
|
||||
SwingUtils.openLink(C.URL_CONTACT);
|
||||
}//GEN-LAST:event_btnContactActionPerformed
|
||||
|
||||
private void btnTerminateGameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTerminateGameActionPerformed
|
||||
terminateGames();
|
||||
}//GEN-LAST:event_btnTerminateGameActionPerformed
|
||||
|
||||
private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
|
||||
if (listener != null && listener.apply())
|
||||
terminateGames();
|
||||
SwingUtils.exitIfNoWindow(this);
|
||||
}//GEN-LAST:event_formWindowClosing
|
||||
|
||||
private void cboShowLinesItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboShowLinesItemStateChanged
|
||||
Settings.getInstance().setLogLines(Integer.parseInt(cboShowLines.getSelectedItem().toString()));
|
||||
}//GEN-LAST:event_cboShowLinesItemStateChanged
|
||||
|
||||
private void txtSearchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtSearchActionPerformed
|
||||
if (evt.getSource() != txtSearch || txtSearch.getText().isEmpty())
|
||||
return;
|
||||
try {
|
||||
if (!s.equals(txtSearch.getText()))
|
||||
pos = 0;
|
||||
StyledDocument d = txtLog.getStyledDocument();
|
||||
s = txtSearch.getText();
|
||||
String content = d.getText(0, d.getLength());
|
||||
int x = content.indexOf(s, pos);
|
||||
if (x == -1) {
|
||||
pos = 0;
|
||||
x = content.indexOf(s, pos);
|
||||
}
|
||||
if (x == -1)
|
||||
return;
|
||||
int len = s.length();
|
||||
pos = x + len;
|
||||
SimpleAttributeSet set = new SimpleAttributeSet(d.getLogicalStyle(x).copyAttributes());
|
||||
StyleConstants.setBackground(set, txtLog.getSelectionColor());
|
||||
StyleConstants.setForeground(set, txtLog.getSelectedTextColor());
|
||||
d.setCharacterAttributes(x, len, set, true);
|
||||
set = new SimpleAttributeSet(d.getLogicalStyle(lastX).copyAttributes());
|
||||
StyleConstants.setBackground(set, txtLog.getBackground());
|
||||
StyleConstants.setForeground(set, txtLog.getForeground());
|
||||
d.setCharacterAttributes(lastX, lastLen, set, true);
|
||||
txtLog.select(x, pos);
|
||||
lastX = x;
|
||||
lastLen = len;
|
||||
} catch (BadLocationException e) {
|
||||
}
|
||||
}//GEN-LAST:event_txtSearchActionPerformed
|
||||
|
||||
String s = "";
|
||||
int pos = 0, lastX = 0, lastLen = 0;
|
||||
|
||||
void terminateGames() {
|
||||
ProcessMonitor.stopAll();
|
||||
}
|
||||
|
||||
public void waitForLoaded() throws InterruptedException {
|
||||
latch.await();
|
||||
}
|
||||
int removedLength = 0;
|
||||
Deque<Integer> offsets = new LinkedList<>();
|
||||
int fatals = 0, errors = 0, warns = 0, infos = 0, debugs = 0;
|
||||
|
||||
public class LogWindowImpl extends StackPane {
|
||||
|
||||
@FXML
|
||||
private WebView webView;
|
||||
@FXML
|
||||
private ToggleButton btnFatals;
|
||||
@FXML
|
||||
private ToggleButton btnErrors;
|
||||
@FXML
|
||||
private ToggleButton btnWarns;
|
||||
@FXML
|
||||
private ToggleButton btnInfos;
|
||||
@FXML
|
||||
private ToggleButton btnDebugs;
|
||||
@FXML
|
||||
private ComboBox<String> cboLines;
|
||||
|
||||
final WebEngine engine;
|
||||
Node body;
|
||||
Document document;
|
||||
|
||||
LogWindowImpl() {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/log.fxml");
|
||||
|
||||
engine = webView.getEngine();
|
||||
engine.loadContent(Lang.ignoringException(() -> IOUtils.readFullyAsString(getClass().getResourceAsStream("/assets/log-window-content.html")))
|
||||
.replace("${FONT}", config().getFontSize() + "px \"" + config().getFontFamily() + "\""));
|
||||
engine.getLoadWorker().stateProperty().addListener((a, b, newValue) -> {
|
||||
if (newValue == Worker.State.SUCCEEDED) {
|
||||
document = engine.getDocument();
|
||||
body = document.getElementsByTagName("body").item(0);
|
||||
engine.executeScript("limitedLogs=" + config().getLogLines());
|
||||
latch.countDown();
|
||||
onDone.fireEvent(new Event(LogWindow.this));
|
||||
public void log(final String status, final Level c) {
|
||||
if (!isVisible())
|
||||
return;
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
Document d = txtLog.getStyledDocument();
|
||||
try { // prevent too much memory used.
|
||||
if (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory() > 1l * 1024 * 1024 * 256) {
|
||||
d.remove(0, d.getLength());
|
||||
offsets.clear();
|
||||
removedLength = 0;
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
HMCLog.err("Failed to clear the text component", ex);
|
||||
}
|
||||
String newStatus = status.replace("\t", " ");
|
||||
SimpleAttributeSet sas = new SimpleAttributeSet();
|
||||
StyleConstants.setForeground(sas, c.COLOR);
|
||||
try {
|
||||
offsets.add(d.getLength() + removedLength);
|
||||
d.insertString(d.getLength(), newStatus, sas);
|
||||
} catch (Exception ex) {
|
||||
HMCLog.err("Failed to insert \"" + newStatus + "\" to " + d.getLength(), ex);
|
||||
}
|
||||
|
||||
boolean flag = false;
|
||||
for (String i : cboLines.getItems())
|
||||
if (Integer.toString(config().getLogLines()).equals(i)) {
|
||||
cboLines.getSelectionModel().select(i);
|
||||
flag = true;
|
||||
switch (c) {
|
||||
case FATAL:
|
||||
btnFatal.setText(++fatals + " fatals");
|
||||
break;
|
||||
case ERROR:
|
||||
btnError.setText(++errors + " errors");
|
||||
break;
|
||||
case WARN:
|
||||
btnWarn.setText(++warns + " warns");
|
||||
break;
|
||||
case INFO:
|
||||
btnInfo.setText(++infos + " infos");
|
||||
break;
|
||||
case DEBUG:
|
||||
btnDebug.setText(++debugs + " debugs");
|
||||
break;
|
||||
}
|
||||
|
||||
int maxLines = Integer.parseInt(cboShowLines.getSelectedItem().toString());
|
||||
while (offsets.size() > maxLines) {
|
||||
int start = offsets.pollFirst();
|
||||
int end = offsets.peekFirst();
|
||||
try {
|
||||
d.remove(start - removedLength, end - start); // start - removedLength must become 0
|
||||
removedLength = end;
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
|
||||
config().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
|
||||
engine.executeScript("limitedLogs=" + config().getLogLines());
|
||||
});
|
||||
|
||||
if (!flag)
|
||||
cboLines.getSelectionModel().select(0);
|
||||
|
||||
btnFatals.textProperty().bind(Bindings.concat(fatal, " fatals"));
|
||||
btnErrors.textProperty().bind(Bindings.concat(error, " errors"));
|
||||
btnWarns.textProperty().bind(Bindings.concat(warn, " warns"));
|
||||
btnInfos.textProperty().bind(Bindings.concat(info, " infos"));
|
||||
btnDebugs.textProperty().bind(Bindings.concat(debug, " debugs"));
|
||||
|
||||
btnFatals.selectedProperty().addListener(o -> specificChanged());
|
||||
btnErrors.selectedProperty().addListener(o -> specificChanged());
|
||||
btnWarns.selectedProperty().addListener(o -> specificChanged());
|
||||
btnInfos.selectedProperty().addListener(o -> specificChanged());
|
||||
btnDebugs.selectedProperty().addListener(o -> specificChanged());
|
||||
}
|
||||
|
||||
private void specificChanged() {
|
||||
String res = "";
|
||||
if (btnFatals.isSelected())
|
||||
res += "\"fatal\", ";
|
||||
if (btnErrors.isSelected())
|
||||
res += "\"error\", ";
|
||||
if (btnWarns.isSelected())
|
||||
res += "\"warn\", ";
|
||||
if (btnInfos.isSelected())
|
||||
res += "\"info\", ";
|
||||
if (btnDebugs.isSelected())
|
||||
res += "\"debug\", ";
|
||||
if (StringUtils.isNotBlank(res))
|
||||
res = StringUtils.substringBeforeLast(res, ", ");
|
||||
engine.executeScript("specific([" + res + "])");
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onTerminateGame() {
|
||||
LauncherHelper.stopManagedProcesses();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onClear() {
|
||||
engine.executeScript("clear()");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setExit(NonFunction<Boolean> exit) {
|
||||
this.listener = exit;
|
||||
}
|
||||
|
||||
public void clean() {
|
||||
txtLog.setText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisible(boolean b) {
|
||||
txtLog.setFont(Settings.getInstance().getConsoleFont());
|
||||
lblCrash.setVisible(false);
|
||||
btnContact.setVisible(false);
|
||||
super.setVisible(b);
|
||||
}
|
||||
|
||||
public void showAsCrashWindow(boolean out_date) {
|
||||
txtLog.setFont(Settings.getInstance().getConsoleFont());
|
||||
if (out_date) {
|
||||
lblCrash.setVisible(false);
|
||||
btnContact.setVisible(false);
|
||||
lblCrash.setText(C.i18n("ui.label.crashing_out_dated"));
|
||||
} else {
|
||||
lblCrash.setVisible(true);
|
||||
btnContact.setVisible(true);
|
||||
lblCrash.setText(C.i18n("ui.label.crashing"));
|
||||
}
|
||||
|
||||
super.setVisible(true);
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton btnClear;
|
||||
private javax.swing.JButton btnClose;
|
||||
private javax.swing.JButton btnContact;
|
||||
private javax.swing.JButton btnCopy;
|
||||
private javax.swing.JToggleButton btnDebug;
|
||||
private javax.swing.JButton btnError;
|
||||
private javax.swing.JButton btnFatal;
|
||||
private javax.swing.JToggleButton btnInfo;
|
||||
private javax.swing.JButton btnTerminateGame;
|
||||
private javax.swing.JToggleButton btnWarn;
|
||||
private javax.swing.JComboBox<String> cboShowLines;
|
||||
private javax.swing.JLabel lblCrash;
|
||||
private javax.swing.JLabel lblSearch;
|
||||
private javax.swing.JLabel lblShowLines;
|
||||
private javax.swing.JScrollPane pnlLog;
|
||||
private javax.swing.JTextPane txtLog;
|
||||
private javax.swing.JTextField txtSearch;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Hello Minecraft!.
|
||||
* Copyright (C) 2013 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 java.io.OutputStream;
|
||||
import java.util.Objects;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.jackhuang.hmcl.util.log.Level;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class LogWindowOutputStream extends OutputStream {
|
||||
|
||||
private final LogWindow txt;
|
||||
private final Level sas;
|
||||
|
||||
public LogWindowOutputStream(LogWindow logWindow, Level l) {
|
||||
txt = Objects.requireNonNull(logWindow);
|
||||
sas = Objects.requireNonNull(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(byte[] arr) {
|
||||
write(arr, 0, arr.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(byte[] arr, int off, int len) {
|
||||
append(new String(arr, off, len));
|
||||
}
|
||||
|
||||
final Object obj = new Object();
|
||||
Level lastLevel = null;
|
||||
|
||||
private void append(final String str) {
|
||||
synchronized(this) {
|
||||
if (manual) return;
|
||||
}
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
Level level = Level.guessLevel(str);
|
||||
if (level == null) level = lastLevel;
|
||||
else lastLevel = level;
|
||||
txt.log(str, Level.mergeLevel(sas, level));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(int i) {
|
||||
append(new String(new byte[] { (byte) i }));
|
||||
}
|
||||
|
||||
boolean manual = false;
|
||||
|
||||
public void log(String s, Level l) {
|
||||
synchronized(this) {
|
||||
manual = true;
|
||||
System.out.print(s);
|
||||
manual = false;
|
||||
}
|
||||
if (l == null) append(s);
|
||||
else
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
txt.log(s, l);
|
||||
});
|
||||
}
|
||||
}
|
||||
+504
@@ -0,0 +1,504 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 org.jackhuang.hmcl.util.ui.GaussionPanel;
|
||||
import org.jackhuang.hmcl.util.ui.IRepaint;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.GridLayout;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Transparency;
|
||||
import java.awt.Window;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowListener;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.UIManager;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.Main;
|
||||
import org.jackhuang.hmcl.api.PluginManager;
|
||||
import org.jackhuang.hmcl.api.event.config.ThemeChangedEvent;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.api.HMCLog;
|
||||
import org.jackhuang.hmcl.util.ui.BasicColors;
|
||||
import org.jackhuang.hmcl.util.ui.DropShadowBorder;
|
||||
import org.jackhuang.hmcl.util.ui.GraphicsUtils;
|
||||
import org.jackhuang.hmcl.util.ui.SwingUtils;
|
||||
import org.jackhuang.hmcl.util.ui.TintablePanel;
|
||||
import org.jackhuang.hmcl.api.auth.IAuthenticator;
|
||||
import org.jackhuang.hmcl.api.ui.Theme;
|
||||
import org.jackhuang.hmcl.api.ui.TopTabPage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class MainFrame extends DraggableFrame implements IRepaint {
|
||||
|
||||
public static final MainFrame INSTANCE = new MainFrame();
|
||||
|
||||
TintablePanel centralPanel;
|
||||
JPanel header, infoSwap, realPanel;
|
||||
CardLayout infoLayout;
|
||||
JLabel windowTitle;
|
||||
GaussionPanel backgroundLabel;
|
||||
DropShadowBorder border;
|
||||
boolean enableShadow;
|
||||
String defaultTitle;
|
||||
|
||||
private int tempW, tempH;
|
||||
|
||||
void setContentSize(int w, int h) {
|
||||
setSize(w, h);
|
||||
tempW = w;
|
||||
tempH = h;
|
||||
}
|
||||
|
||||
MainFrame() {
|
||||
setUndecorated(!Settings.getInstance().isDecorated());
|
||||
defaultTitle = isUndecorated() ? Main.makeTitle() : "";
|
||||
enableShadow = Settings.getInstance().isEnableShadow() && isUndecorated();
|
||||
if (enableShadow)
|
||||
setContentSize(834, 542);
|
||||
else
|
||||
setContentSize(802, 511);
|
||||
setDefaultCloseOperation(EXIT_ON_CLOSE);
|
||||
setTitle(Main.makeTitle());
|
||||
initComponents();
|
||||
loadBackground();
|
||||
|
||||
setLocationRelativeTo(null);
|
||||
if (MainFrame.this.isUndecorated())
|
||||
setResizable(false);
|
||||
|
||||
this.addWindowListener(new WindowListener() {
|
||||
@Override
|
||||
public void windowOpened(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosing(WindowEvent e) {
|
||||
closing();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowClosed(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowIconified(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDeiconified(WindowEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowActivated(WindowEvent e) {
|
||||
if (!MainFrame.this.isUndecorated()) {
|
||||
int w = tempW + getWidth() - getContentPane().getWidth(), h = tempH + getHeight() - getContentPane().getHeight();
|
||||
setSize(w, h);
|
||||
setResizable(false);
|
||||
setLocationRelativeTo(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void windowDeactivated(WindowEvent e) {
|
||||
}
|
||||
});
|
||||
|
||||
if (enableShadow)
|
||||
try {
|
||||
setBackground(new Color(0, 0, 0, 0));
|
||||
getRootPane().setBorder(border = new DropShadowBorder(borderColor, 4));
|
||||
} catch (Throwable ex) {
|
||||
HMCLog.err("Failed to set window transparent.", ex);
|
||||
Settings.getInstance().setEnableShadow(false);
|
||||
setSize(802, 511);
|
||||
}
|
||||
((JPanel) getContentPane()).setOpaque(true);
|
||||
|
||||
HMCLApi.EVENT_BUS.channel(ThemeChangedEvent.class).register(x -> reloadColor(x.getValue()));
|
||||
|
||||
SwingUtilities.invokeLater(() -> selectTab("main"));
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(null);
|
||||
|
||||
realPanel = new JPanel();
|
||||
realPanel.setLayout(null);
|
||||
|
||||
header = new JPanel();
|
||||
header.setBounds(0, 0, 800, 30);
|
||||
realPanel.add(header);
|
||||
header.setOpaque(true);
|
||||
header.setLayout(new BoxLayout(header, BoxLayout.LINE_AXIS));
|
||||
header.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 5));
|
||||
header.setBackground(borderColor);
|
||||
header.setForeground(BasicColors.COLOR_WHITE_TEXT);
|
||||
|
||||
ImageIcon headerIcon = Main.getIcon("icon.png");
|
||||
this.setIconImage(headerIcon.getImage());
|
||||
headerIcon = SwingUtils.scaleImage(headerIcon, 16, 16);
|
||||
JLabel headerLabel = new JLabel(headerIcon);
|
||||
headerLabel.setBorder(BorderFactory.createEmptyBorder(0, 8, 0, 0));
|
||||
header.add(headerLabel);
|
||||
|
||||
header.add(Box.createRigidArea(new Dimension(8, 0)));
|
||||
|
||||
PluginManager.fireAddTab(this, this::initializeTab);
|
||||
|
||||
header.add(Box.createHorizontalGlue());
|
||||
|
||||
JPanel rightHeaderPanel = new JPanel();
|
||||
rightHeaderPanel.setOpaque(false);
|
||||
rightHeaderPanel.setLayout(new BoxLayout(rightHeaderPanel, BoxLayout.PAGE_AXIS));
|
||||
rightHeaderPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0));
|
||||
|
||||
JPanel windowGadgetPanel = new JPanel();
|
||||
windowGadgetPanel.setOpaque(false);
|
||||
windowGadgetPanel.setLayout(new BoxLayout(windowGadgetPanel, BoxLayout.LINE_AXIS));
|
||||
windowGadgetPanel.setAlignmentX(1.0F);
|
||||
|
||||
if (!Settings.getInstance().isDecorated()) {
|
||||
ImageIcon minimizeIcon = Main.getIcon("minimize.png");
|
||||
JButton minimizeButton = new JButton(minimizeIcon);
|
||||
minimizeButton.setBorder(BorderFactory.createEmptyBorder());
|
||||
minimizeButton.setContentAreaFilled(false);
|
||||
minimizeButton.setCursor(new Cursor(12));
|
||||
minimizeButton.setFocusable(false);
|
||||
minimizeButton.addActionListener((e) -> MainFrame.this.minimizeWindow());
|
||||
windowGadgetPanel.add(minimizeButton);
|
||||
|
||||
ImageIcon closeIcon = Main.getIcon("close.png");
|
||||
JButton closeButton = new JButton(closeIcon);
|
||||
closeButton.setBorder(BorderFactory.createEmptyBorder());
|
||||
closeButton.setContentAreaFilled(false);
|
||||
closeButton.addActionListener((e) -> MainFrame.this.closeWindow());
|
||||
closeButton.setCursor(new Cursor(12));
|
||||
closeButton.setFocusable(false);
|
||||
windowGadgetPanel.add(closeButton);
|
||||
}
|
||||
rightHeaderPanel.add(windowGadgetPanel);
|
||||
|
||||
windowTitle = new JLabel(defaultTitle);
|
||||
windowTitle.setForeground(BasicColors.COLOR_WHITE_TEXT);
|
||||
header.add(windowTitle);
|
||||
header.add(Box.createHorizontalGlue());
|
||||
header.add(rightHeaderPanel);
|
||||
|
||||
this.centralPanel = new TintablePanel();
|
||||
this.centralPanel.setTintColor(BasicColors.COLOR_CENTRAL_BACK);
|
||||
realPanel.add(this.centralPanel);
|
||||
JPanel truePanel = new JPanel();
|
||||
truePanel.setLayout(new BorderLayout());
|
||||
|
||||
this.infoSwap = new JPanel();
|
||||
this.infoLayout = new CardLayout();
|
||||
this.infoSwap.setLayout(infoLayout);
|
||||
this.infoSwap.setOpaque(false);
|
||||
|
||||
tabWrapper = new JPanel[tabHeader.size()];
|
||||
for (int i = 0; i < tabHeader.size(); i++) {
|
||||
tabWrapper[i] = new JPanel();
|
||||
tabWrapper[i].setLayout(new GridLayout());
|
||||
infoSwap.add(tabWrapper[i], tabHeader.get(i).getActionCommand());
|
||||
}
|
||||
|
||||
truePanel.add(this.infoSwap, "Center");
|
||||
centralPanel.setLayout(null);
|
||||
centralPanel.add(truePanel);
|
||||
truePanel.setBounds(0, 0, 800, 480);
|
||||
centralPanel.setBounds(0, 30, 800, 480);
|
||||
|
||||
realPanel.setBounds(1, 0, 800, 511);
|
||||
add(realPanel);
|
||||
|
||||
reloadColor(Settings.getInstance().getTheme());
|
||||
}
|
||||
|
||||
private transient final ActionListener tabListener = e -> MainFrame.this.selectTab(e.getActionCommand());
|
||||
|
||||
private void initializeTab(TopTabPage inst, String cmd, String title) {
|
||||
HeaderTab tab = new HeaderTab(title);
|
||||
tab.setActionCommand(cmd);
|
||||
tab.setForeground(BasicColors.COLOR_WHITE_TEXT);
|
||||
tab.setBackground(borderColorDarker);
|
||||
tab.setLayout(null);
|
||||
tab.addActionListener(tabListener);
|
||||
header.add(tab);
|
||||
tabHeader.add(tab);
|
||||
tabContent.add(inst);
|
||||
}
|
||||
|
||||
private final List<HeaderTab> tabHeader = new ArrayList<>();
|
||||
private JPanel tabWrapper[];
|
||||
private final List<TopTabPage> tabContent = new ArrayList<>();
|
||||
|
||||
public void selectTab(String tabName) {
|
||||
int chosen = -1;
|
||||
TopTabPage onCreate = null, onSelect = null, lastPage = null;
|
||||
for (int i = 0; i < tabHeader.size(); i++)
|
||||
if (tabName.equalsIgnoreCase(tabHeader.get(i).getActionCommand())) {
|
||||
if (!tabContent.get(i).isCreated()) {
|
||||
onCreate = tabContent.get(i);
|
||||
onCreate.setId(i);
|
||||
tabWrapper[i].add(tabContent.get(i));
|
||||
} else if (tabContent.get(i).isSelected())
|
||||
continue;
|
||||
chosen = i;
|
||||
break;
|
||||
}
|
||||
if (chosen != -1) {
|
||||
for (int i = 0; i < tabHeader.size(); i++)
|
||||
if (i != chosen && tabContent.get(i) != null && tabContent.get(i).isSelected()) {
|
||||
lastPage = tabContent.get(i);
|
||||
lastPage.onLeave();
|
||||
}
|
||||
for (int i = 0; i < tabHeader.size(); i++)
|
||||
if (i == chosen) {
|
||||
for (int j = 0; j < tabHeader.size(); j++)
|
||||
if (j != i)
|
||||
tabHeader.get(j).setIsActive(false);
|
||||
tabHeader.get(i).setIsActive(true);
|
||||
onSelect = tabContent.get(i);
|
||||
}
|
||||
|
||||
this.infoLayout.show(this.infoSwap, tabName);
|
||||
if (onCreate != null)
|
||||
onCreate.onCreate();
|
||||
if (onSelect != null)
|
||||
onSelect.onSelect(lastPage);
|
||||
}
|
||||
}
|
||||
|
||||
protected void closing() {
|
||||
for (int i = 0; i < tabHeader.size(); i++)
|
||||
if (tabContent.get(i) != null && tabContent.get(i).isSelected())
|
||||
tabContent.get(i).onLeave();
|
||||
}
|
||||
|
||||
protected void closeWindow() {
|
||||
closing();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
protected void minimizeWindow() {
|
||||
setState(1);
|
||||
}
|
||||
|
||||
ImageIcon background;
|
||||
|
||||
public void loadBackground() {
|
||||
background = SwingUtils.searchBackgroundImage(Main.getIcon(Settings.getInstance().getTheme().settings.get("Customized.MainFrame.background_image")), Settings.getInstance().getBgpath(), 800, 480);
|
||||
if (background != null) {
|
||||
if (backgroundLabel == null) {
|
||||
backgroundLabel = new GaussionPanel();
|
||||
backgroundLabel.setDrawBackgroundLayer(true);
|
||||
backgroundLabel.addAeroObject(backgroundLabel);
|
||||
backgroundLabel.setBounds(0, 0, 800, 480);
|
||||
centralPanel.add(backgroundLabel, -1);
|
||||
}
|
||||
backgroundLabel.setBackgroundImage(background.getImage());
|
||||
} else
|
||||
HMCLog.warn("No background image here! The background will be empty!");
|
||||
}
|
||||
|
||||
public JPanel getTitleBar() {
|
||||
return header;
|
||||
}
|
||||
|
||||
boolean isShowedMessage = false;
|
||||
|
||||
public void closeMessage() {
|
||||
if (isShowedMessage) {
|
||||
isShowedMessage = false;
|
||||
reloadColor(Settings.getInstance().getTheme());
|
||||
windowTitle.setText(defaultTitle);
|
||||
windowTitle.setForeground(Settings.UPDATE_CHECKER.isOutOfDate() ? Color.red : Color.white);
|
||||
}
|
||||
}
|
||||
|
||||
public void showMessage(String message) {
|
||||
isShowedMessage = true;
|
||||
borderColor = BasicColors.COLOR_RED;
|
||||
borderColorDarker = BasicColors.COLOR_RED_DARKER;
|
||||
header.setBackground(borderColor);
|
||||
for (HeaderTab tab : tabHeader)
|
||||
tab.setBackground(borderColorDarker);
|
||||
if (border != null)
|
||||
border.setColor(borderColor);
|
||||
repaint();
|
||||
windowTitle.setText(message);
|
||||
windowTitle.setForeground(Color.white);
|
||||
}
|
||||
|
||||
public static void showMainFrame() {
|
||||
IAuthenticator l = Settings.getInstance().getAuthenticator();
|
||||
if (StrUtils.isBlank(l.getUserName()))
|
||||
SwingUtilities.invokeLater(() -> MainFrame.INSTANCE.showMessage(C.i18n("ui.message.first_load")));
|
||||
if (l.hasPassword() && !l.isLoggedIn())
|
||||
SwingUtilities.invokeLater(() -> MainFrame.INSTANCE.showMessage(C.i18n("ui.message.enter_password")));
|
||||
INSTANCE.setVisible(true);
|
||||
}
|
||||
|
||||
Color borderColor;
|
||||
Color borderColorDarker;
|
||||
|
||||
private void initBorderColor() {
|
||||
borderColor = UIManager.getColor("Customized.MainFrame.background");
|
||||
borderColorDarker = UIManager.getColor("Customized.MainFrame.selected_background");
|
||||
}
|
||||
|
||||
public void reloadColor(Theme t) {
|
||||
if (isShowedMessage)
|
||||
return;
|
||||
for (Map.Entry<String, String> entry : t.settings.entrySet())
|
||||
if (entry.getValue().startsWith("#"))
|
||||
UIManager.put(entry.getKey(), GraphicsUtils.getWebColor(entry.getValue()));
|
||||
|
||||
initBorderColor();
|
||||
if (border != null)
|
||||
border.setColor(borderColor);
|
||||
header.setBackground(borderColor);
|
||||
for (HeaderTab tab : tabHeader)
|
||||
tab.setBackground(borderColorDarker);
|
||||
repaint();
|
||||
}
|
||||
|
||||
private void paintImpl(Graphics g) {
|
||||
int off = enableShadow ? 16 : 0, yoff = getInsets().top + off, xoff = getInsets().left + off;
|
||||
int width = 800;
|
||||
int height = header.getHeight() + 480 - 1;
|
||||
super.paint(g);
|
||||
g.setColor(borderColor);
|
||||
g.drawLine(xoff, yoff, xoff, height + yoff + 1);
|
||||
g.drawLine(xoff + width + 1, yoff, xoff + width + 1, height + yoff + 1);
|
||||
g.drawLine(xoff, height + yoff + 1, xoff + width + 1, height + yoff + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(Graphics g) {
|
||||
int off = enableShadow ? 16 : 0;
|
||||
int width = this.getWidth();
|
||||
int height = this.getHeight();
|
||||
int contentWidth = width - off - off;
|
||||
int contentHeight = height - off - off;
|
||||
if (!enableShadow || contentWidth <= 0 || contentHeight <= 0)
|
||||
paintImpl(g);
|
||||
else {
|
||||
BufferedImage contentImage = new BufferedImage(contentWidth,
|
||||
contentHeight, Transparency.OPAQUE);
|
||||
Graphics2D contentG2d = contentImage.createGraphics();
|
||||
contentG2d.translate(-off, -off);
|
||||
paintImpl(g);
|
||||
paintImpl(contentG2d);
|
||||
contentG2d.dispose();
|
||||
g.drawImage(contentImage, off, off, this);
|
||||
}
|
||||
}
|
||||
|
||||
public void invokeUpdate() {
|
||||
defaultTitle = Main.makeTitle() + C.i18n("update.found");
|
||||
windowTitle.addMouseListener(MouseListenerImpl.INSTANCE);
|
||||
if (!isShowedMessage) {
|
||||
windowTitle.setText(defaultTitle);
|
||||
windowTitle.setForeground(Color.red);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getRepaintComponent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window getRepaintWindow() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Rectangle> getRepaintRects() {
|
||||
int off = MainFrame.INSTANCE.enableShadow ? 16 : 0, yoff = MainFrame.INSTANCE.getInsets().top + off, xoff = MainFrame.INSTANCE.getInsets().left + off;
|
||||
int width = 800, height = MainFrame.INSTANCE.header.getHeight() + 480 - 1;
|
||||
return Arrays.asList(new Rectangle(xoff, yoff, xoff, height + yoff + 1),
|
||||
new Rectangle(xoff + width + 1, yoff, xoff + width + 1, height + yoff + 1),
|
||||
new Rectangle(xoff, height + yoff + 1, xoff + width + 1, height + yoff + 1));
|
||||
}
|
||||
|
||||
private static class MouseListenerImpl implements MouseListener {
|
||||
|
||||
public static final MouseListenerImpl INSTANCE = new MouseListenerImpl();
|
||||
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e) {
|
||||
Settings.UPDATE_CHECKER.checkOutdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void failed(String s) {
|
||||
if (s != null)
|
||||
MessageBox.show(s);
|
||||
closeMessage();
|
||||
}
|
||||
|
||||
transient LaunchingUIDaemon daemon = new LaunchingUIDaemon();
|
||||
}
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXPopup;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Profiles;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.PopupMenu;
|
||||
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.ui.versions.Versions;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.SINE;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public final class MainPage extends StackPane implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("main_page"));
|
||||
|
||||
private final PopupMenu menu = new PopupMenu();
|
||||
private final JFXPopup popup = new JFXPopup(menu);
|
||||
|
||||
private final StringProperty currentGame = new SimpleStringProperty(this, "currentGame");
|
||||
private final BooleanProperty showUpdate = new SimpleBooleanProperty(this, "showUpdate");
|
||||
private final StringProperty latestVersion = new SimpleStringProperty(this, "latestVersion");
|
||||
private final ObservableList<Node> versions = FXCollections.observableArrayList();
|
||||
|
||||
private StackPane updatePane;
|
||||
private JFXButton menuButton;
|
||||
|
||||
{
|
||||
setPadding(new Insets(25));
|
||||
|
||||
updatePane = new StackPane();
|
||||
updatePane.setVisible(false);
|
||||
updatePane.getStyleClass().add("bubble");
|
||||
FXUtils.setLimitWidth(updatePane, 230);
|
||||
FXUtils.setLimitHeight(updatePane, 55);
|
||||
StackPane.setAlignment(updatePane, Pos.TOP_RIGHT);
|
||||
updatePane.setOnMouseClicked(e -> onUpgrade());
|
||||
FXUtils.onChange(showUpdateProperty(), this::doAnimation);
|
||||
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
hBox.setSpacing(12);
|
||||
hBox.setAlignment(Pos.CENTER_LEFT);
|
||||
StackPane.setAlignment(hBox, Pos.CENTER_LEFT);
|
||||
StackPane.setMargin(hBox, new Insets(9, 12, 9, 16));
|
||||
{
|
||||
Label lblIcon = new Label();
|
||||
lblIcon.setGraphic(SVG.update(Theme.whiteFillBinding(), 20, 20));
|
||||
|
||||
TwoLineListItem prompt = new TwoLineListItem();
|
||||
prompt.setSubtitle(i18n("update.bubble.subtitle"));
|
||||
prompt.setPickOnBounds(false);
|
||||
prompt.titleProperty().bind(latestVersionProperty());
|
||||
|
||||
hBox.getChildren().setAll(lblIcon, prompt);
|
||||
}
|
||||
|
||||
JFXButton closeUpdateButton = new JFXButton();
|
||||
closeUpdateButton.setGraphic(SVG.close(Theme.whiteFillBinding(), 10, 10));
|
||||
StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT);
|
||||
closeUpdateButton.getStyleClass().add("toggle-icon-tiny");
|
||||
StackPane.setMargin(closeUpdateButton, new Insets(5));
|
||||
closeUpdateButton.setOnMouseClicked(e -> closeUpdateBubble());
|
||||
|
||||
updatePane.getChildren().setAll(hBox, closeUpdateButton);
|
||||
}
|
||||
|
||||
StackPane launchPane = new StackPane();
|
||||
launchPane.setMaxWidth(230);
|
||||
launchPane.setMaxHeight(55);
|
||||
StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
|
||||
{
|
||||
JFXButton launchButton = new JFXButton();
|
||||
launchButton.setPrefWidth(230);
|
||||
launchButton.setPrefHeight(55);
|
||||
launchButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
launchButton.getStyleClass().add("jfx-button-raised");
|
||||
launchButton.setOnMouseClicked(e -> launch());
|
||||
launchButton.setClip(new Rectangle(-100, -100, 310, 200));
|
||||
{
|
||||
VBox graphic = new VBox();
|
||||
graphic.setAlignment(Pos.CENTER);
|
||||
graphic.setTranslateX(-7);
|
||||
graphic.setMaxWidth(200);
|
||||
Label launchLabel = new Label(i18n("version.launch"));
|
||||
launchLabel.setStyle("-fx-font-size: 16px;");
|
||||
Label currentLabel = new Label();
|
||||
currentLabel.setStyle("-fx-font-size: 12px;");
|
||||
currentLabel.textProperty().bind(currentGameProperty());
|
||||
graphic.getChildren().setAll(launchLabel, currentLabel);
|
||||
|
||||
launchButton.setGraphic(graphic);
|
||||
}
|
||||
|
||||
Rectangle separator = new Rectangle();
|
||||
separator.getStyleClass().add("darker-fill");
|
||||
separator.setWidth(1);
|
||||
separator.setHeight(57);
|
||||
separator.setTranslateX(95);
|
||||
separator.setMouseTransparent(true);
|
||||
|
||||
menuButton = new JFXButton();
|
||||
menuButton.setPrefHeight(55);
|
||||
menuButton.setPrefWidth(230);
|
||||
menuButton.setButtonType(JFXButton.ButtonType.RAISED);
|
||||
menuButton.getStyleClass().add("jfx-button-raised");
|
||||
menuButton.setStyle("-fx-font-size: 15px;");
|
||||
menuButton.setOnMouseClicked(e -> onMenu());
|
||||
menuButton.setClip(new Rectangle(211, -100, 100, 200));
|
||||
StackPane graphic = new StackPane();
|
||||
Node svg = SVG.triangle(Theme.whiteFillBinding(), 10, 10);
|
||||
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
|
||||
graphic.getChildren().setAll(svg);
|
||||
graphic.setTranslateX(12);
|
||||
menuButton.setGraphic(graphic);
|
||||
|
||||
launchPane.getChildren().setAll(launchButton, separator, menuButton);
|
||||
}
|
||||
|
||||
getChildren().setAll(updatePane, launchPane);
|
||||
|
||||
menu.setMaxHeight(365);
|
||||
menu.setMaxWidth(545);
|
||||
menu.setAlwaysShowingVBar(true);
|
||||
menu.setOnMouseClicked(e -> popup.hide());
|
||||
Bindings.bindContent(menu.getContent(), versions);
|
||||
}
|
||||
|
||||
private void doAnimation(boolean show) {
|
||||
Duration duration = Duration.millis(320);
|
||||
Timeline nowAnimation = new Timeline();
|
||||
nowAnimation.getKeyFrames().addAll(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(updatePane.translateXProperty(), show ? 260 : 0, SINE)),
|
||||
new KeyFrame(duration,
|
||||
new KeyValue(updatePane.translateXProperty(), show ? 0 : 260, SINE)));
|
||||
if (show) nowAnimation.getKeyFrames().add(
|
||||
new KeyFrame(Duration.ZERO, e -> updatePane.setVisible(true)));
|
||||
else nowAnimation.getKeyFrames().add(
|
||||
new KeyFrame(duration, e -> updatePane.setVisible(false)));
|
||||
nowAnimation.play();
|
||||
}
|
||||
|
||||
private void launch() {
|
||||
Profile profile = Profiles.getSelectedProfile();
|
||||
Versions.launch(profile, profile.getSelectedVersion());
|
||||
}
|
||||
|
||||
private void onMenu() {
|
||||
popup.show(menuButton, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, 0, -menuButton.getHeight());
|
||||
}
|
||||
|
||||
private void onUpgrade() {
|
||||
RemoteVersion target = UpdateChecker.getLatestVersion();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
UpdateHandler.updateFrom(target);
|
||||
}
|
||||
|
||||
private void closeUpdateBubble() {
|
||||
showUpdate.unbind();
|
||||
showUpdate.set(false);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getCurrentGame() {
|
||||
return currentGame.get();
|
||||
}
|
||||
|
||||
public StringProperty currentGameProperty() {
|
||||
return currentGame;
|
||||
}
|
||||
|
||||
public void setCurrentGame(String currentGame) {
|
||||
this.currentGame.set(currentGame);
|
||||
}
|
||||
|
||||
public boolean isShowUpdate() {
|
||||
return showUpdate.get();
|
||||
}
|
||||
|
||||
public BooleanProperty showUpdateProperty() {
|
||||
return showUpdate;
|
||||
}
|
||||
|
||||
public void setShowUpdate(boolean showUpdate) {
|
||||
this.showUpdate.set(showUpdate);
|
||||
}
|
||||
|
||||
public String getLatestVersion() {
|
||||
return latestVersion.get();
|
||||
}
|
||||
|
||||
public StringProperty latestVersionProperty() {
|
||||
return latestVersion;
|
||||
}
|
||||
|
||||
public void setLatestVersion(String latestVersion) {
|
||||
this.latestVersion.set(latestVersion);
|
||||
}
|
||||
|
||||
public ObservableList<Node> getVersions() {
|
||||
return versions;
|
||||
}
|
||||
}
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
<AuxValue name="designerSize" type="java.awt.Dimension" value="-84,-19,0,5,115,114,0,18,106,97,118,97,46,97,119,116,46,68,105,109,101,110,115,105,111,110,65,-114,-39,-41,-84,95,68,20,2,0,2,73,0,6,104,101,105,103,104,116,73,0,5,119,105,100,116,104,120,112,0,0,1,-32,0,0,3,32"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
|
||||
<Property name="useNullLayout" type="boolean" value="true"/>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="pnlRoot">
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new org.jackhuang.hmcl.launcher.ui.GaussionPanel()"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
|
||||
<AbsoluteConstraints x="0" y="0" width="800" height="480"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout">
|
||||
<Property name="useNullLayout" type="boolean" value="true"/>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="pnlMore">
|
||||
<Properties>
|
||||
<Property name="background" type="java.awt.Color" editor="org.netbeans.beaninfo.editors.ColorEditor">
|
||||
<Color blue="cc" green="cc" red="cc" type="rgb"/>
|
||||
</Property>
|
||||
<Property name="opaque" type="boolean" value="false"/>
|
||||
</Properties>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new org.jackhuang.hmcl.launcher.ui.RepaintPage()"/>
|
||||
<AuxValue name="JavaCodeGenerator_SerializeTo" type="java.lang.String" value="MainPagePanel_pnlMore"/>
|
||||
</AuxValues>
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout" value="org.netbeans.modules.form.compat2.layouts.DesignAbsoluteLayout$AbsoluteConstraintsDescription">
|
||||
<AbsoluteConstraints x="0" y="0" width="190" height="480"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="pnlPassword" alignment="1" max="32767" attributes="0"/>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="lblProfile" alignment="1" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblVersion" alignment="1" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="cboProfiles" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="cboVersions" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="lblUserName" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="lblLogin" alignment="0" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="cboLoginMode" max="32767" attributes="0"/>
|
||||
<Component id="txtPlayerName" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<Component id="btnExportModpack" alignment="0" max="32767" attributes="0"/>
|
||||
<Component id="btnImportModpack" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblProfile" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="cboProfiles" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="cboVersions" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<Component id="lblVersion" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblLogin" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="cboLoginMode" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblUserName" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="txtPlayerName" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="pnlPassword" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
<EmptySpace pref="254" max="32767" attributes="0"/>
|
||||
<Component id="btnImportModpack" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnExportModpack" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JTextField" name="txtPlayerName">
|
||||
<Events>
|
||||
<EventHandler event="focusGained" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtPlayerNameFocusGained"/>
|
||||
<EventHandler event="focusLost" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtPlayerNameFocusLost"/>
|
||||
<EventHandler event="keyPressed" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="txtPlayerNameKeyPressed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblLogin">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="login.type" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboLoginMode">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboLoginModeItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblUserName">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="login.username" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblProfile">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.label.profile" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboProfiles">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboProfilesItemStateChanged"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="lblVersion">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.label.version" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
<Property name="toolTipText" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.label.version" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboVersions">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboVersionsItemStateChanged"/>
|
||||
</Events>
|
||||
<AuxValues>
|
||||
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new WideComboBox()"/>
|
||||
</AuxValues>
|
||||
</Component>
|
||||
<Container class="javax.swing.JPanel" name="pnlPassword">
|
||||
|
||||
<Layout class="org.netbeans.modules.form.compat2.layouts.DesignCardLayout"/>
|
||||
<SubComponents>
|
||||
<Container class="javax.swing.JPanel" name="pnlLogIn">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
|
||||
<CardConstraints cardName="card2"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="lblPassword" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="txtPassword" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="lblPassword" alignment="3" min="-2" pref="22" max="-2" attributes="0"/>
|
||||
<Component id="txtPassword" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="lblPassword">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.label.password" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JPasswordField" name="txtPassword">
|
||||
<Events>
|
||||
<EventHandler event="caretUpdate" listener="javax.swing.event.CaretListener" parameters="javax.swing.event.CaretEvent" handler="txtPasswordCaretUpdate"/>
|
||||
<EventHandler event="focusGained" listener="java.awt.event.FocusListener" parameters="java.awt.event.FocusEvent" handler="txtPasswordFocusGained"/>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="txtPasswordActionPerformed"/>
|
||||
<EventHandler event="keyPressed" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="txtPasswordKeyPressed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Container class="javax.swing.JPanel" name="pnlLogOut">
|
||||
<Constraints>
|
||||
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignCardLayout" value="org.netbeans.modules.form.compat2.layouts.DesignCardLayout$CardConstraintsDescription">
|
||||
<CardConstraints cardName="card3"/>
|
||||
</Constraint>
|
||||
</Constraints>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="btnLogout" alignment="0" pref="170" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Component id="btnLogout" alignment="0" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JButton" name="btnLogout">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.button.logout" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnLogoutActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
<Component class="javax.swing.JButton" name="btnImportModpack">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="modpack.task.install" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnImportModpackActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnExportModpack">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="modpack.task.save" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnExportModpackActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Container>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
+553
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 org.jackhuang.hmcl.util.ui.Page;
|
||||
import org.jackhuang.hmcl.util.ui.WideComboBox;
|
||||
import org.jackhuang.hmcl.util.ui.GaussionPanel;
|
||||
import java.awt.CardLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Font;
|
||||
import java.awt.event.ItemEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import javax.swing.DefaultComboBoxModel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.filechooser.FileNameExtensionFilter;
|
||||
import org.jackhuang.hmcl.api.HMCLApi;
|
||||
import org.jackhuang.hmcl.api.event.config.AuthenticatorChangedEvent;
|
||||
import org.jackhuang.hmcl.api.event.config.ProfileChangedEvent;
|
||||
import org.jackhuang.hmcl.api.event.config.ProfileLoadingEvent;
|
||||
import org.jackhuang.hmcl.api.event.launch.LaunchSucceededEvent;
|
||||
import org.jackhuang.hmcl.api.event.launch.LaunchingState;
|
||||
import org.jackhuang.hmcl.api.event.launch.LaunchingStateChangedEvent;
|
||||
import org.jackhuang.hmcl.api.event.version.RefreshedVersionsEvent;
|
||||
import org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.core.auth.AbstractAuthenticator;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
import org.jackhuang.hmcl.core.version.MinecraftVersion;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.core.mod.ModpackManager;
|
||||
import org.jackhuang.hmcl.ui.modpack.ModpackWizard;
|
||||
import org.jackhuang.hmcl.laf.button.CustomButton;
|
||||
import org.jackhuang.hmcl.api.func.Consumer;
|
||||
import org.jackhuang.hmcl.util.sys.FileUtils;
|
||||
import org.jackhuang.hmcl.util.task.TaskWindow;
|
||||
import org.jackhuang.hmcl.util.ui.GraphicsUtils;
|
||||
import org.jackhuang.hmcl.util.ui.JSystemFileChooser;
|
||||
import org.jackhuang.hmcl.util.ui.SwingUtils;
|
||||
import org.jackhuang.hmcl.util.ui.wizard.api.WizardDisplayer;
|
||||
import org.jackhuang.hmcl.api.auth.IAuthenticator;
|
||||
import org.jackhuang.hmcl.laf.button.CustomButtonUI;
|
||||
import org.jackhuang.hmcl.util.ui.MessageBox;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class MainPagePanel extends Page {
|
||||
|
||||
public MainPagePanel() {
|
||||
}
|
||||
|
||||
void initGui() {
|
||||
initComponents();
|
||||
|
||||
animationEnabled = Settings.getInstance().isEnableAnimation();
|
||||
|
||||
pnlButtons = new javax.swing.JPanel();
|
||||
pnlButtons.setLayout(null);
|
||||
|
||||
int w = 150, h = 50;
|
||||
btnRun = new CustomButton();
|
||||
btnRun.setBounds(0, 0, w, h);
|
||||
Font font = btnRun.getFont();
|
||||
Font newFont = new Font(font.getName(), font.getStyle(), 15);
|
||||
pnlButtons.add(btnRun);
|
||||
|
||||
btnRun.setText(C.i18n("ui.button.run"));
|
||||
btnRun.setFont(newFont);
|
||||
btnRun.addActionListener(e -> MainFrame.INSTANCE.daemon.runGame(Settings.getLastProfile()));
|
||||
btnRun.setUI(new CustomButtonUI());
|
||||
|
||||
pnlRoot.add(pnlButtons);
|
||||
pnlButtons.setBounds(0, 0, w, h);
|
||||
|
||||
this.setSize(new Dimension(DEFAULT_WIDTH, DEFAULT_HEIGHT));
|
||||
this.pnlButtons.setLocation(DEFAULT_WIDTH - pnlButtons.getWidth() - 25, DEFAULT_HEIGHT - pnlButtons.getHeight() - 25);
|
||||
pnlMore.setBounds(0, 0, pnlMore.getWidth(), DEFAULT_HEIGHT);
|
||||
pnlMore.setBackground(GraphicsUtils.getWebColorWithAlpha("FFFFFF7F"));
|
||||
pnlMore.setOpaque(true);
|
||||
|
||||
if (Settings.getInstance().isEnableBlur())
|
||||
((GaussionPanel) pnlRoot).addAeroObject(pnlMore);
|
||||
((GaussionPanel) pnlRoot).setBackgroundImage(MainFrame.INSTANCE.background.getImage());
|
||||
|
||||
((RepaintPage) pnlMore).setRepainter(pnlRoot);
|
||||
|
||||
HMCLApi.EVENT_BUS.channel(AuthenticatorChangedEvent.class).register(onAuthChanged);
|
||||
HMCLApi.EVENT_BUS.channel(ProfileLoadingEvent.class).register(onLoadingProfiles);
|
||||
HMCLApi.EVENT_BUS.channel(ProfileChangedEvent.class).register(onSelectedProfilesChanged);
|
||||
HMCLApi.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(onRefreshedVersions);
|
||||
HMCLApi.EVENT_BUS.channel(LaunchingStateChangedEvent.class).register(launchingStateChanged);
|
||||
HMCLApi.EVENT_BUS.channel(LaunchSucceededEvent.class).register(this::prepareAuths);
|
||||
|
||||
prepareAuths();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
pnlRoot = new GaussionPanel();
|
||||
pnlMore = new org.jackhuang.hmcl.ui.RepaintPage();
|
||||
txtPlayerName = new javax.swing.JTextField();
|
||||
lblLogin = new javax.swing.JLabel();
|
||||
cboLoginMode = new javax.swing.JComboBox();
|
||||
lblUserName = new javax.swing.JLabel();
|
||||
lblProfile = new javax.swing.JLabel();
|
||||
cboProfiles = new javax.swing.JComboBox();
|
||||
lblVersion = new javax.swing.JLabel();
|
||||
cboVersions = new WideComboBox();
|
||||
pnlPassword = new javax.swing.JPanel();
|
||||
pnlLogIn = new javax.swing.JPanel();
|
||||
lblPassword = new javax.swing.JLabel();
|
||||
txtPassword = new javax.swing.JPasswordField();
|
||||
pnlLogOut = new javax.swing.JPanel();
|
||||
btnLogout = new javax.swing.JButton();
|
||||
btnImportModpack = new javax.swing.JButton();
|
||||
btnExportModpack = new javax.swing.JButton();
|
||||
|
||||
setLayout(null);
|
||||
|
||||
pnlRoot.setLayout(null);
|
||||
|
||||
pnlMore.setBackground(new java.awt.Color(204, 204, 204));
|
||||
pnlMore.setOpaque(false);
|
||||
|
||||
txtPlayerName.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusGained(java.awt.event.FocusEvent evt) {
|
||||
txtPlayerNameFocusGained(evt);
|
||||
}
|
||||
public void focusLost(java.awt.event.FocusEvent evt) {
|
||||
txtPlayerNameFocusLost(evt);
|
||||
}
|
||||
});
|
||||
txtPlayerName.addKeyListener(new java.awt.event.KeyAdapter() {
|
||||
public void keyPressed(java.awt.event.KeyEvent evt) {
|
||||
txtPlayerNameKeyPressed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblLogin.setText(C.i18n("login.type")); // NOI18N
|
||||
|
||||
cboLoginMode.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboLoginModeItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblUserName.setText(C.i18n("login.username")); // NOI18N
|
||||
|
||||
lblProfile.setText(C.i18n("ui.label.profile")); // NOI18N
|
||||
|
||||
cboProfiles.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboProfilesItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
lblVersion.setText(C.i18n("ui.label.version")); // NOI18N
|
||||
lblVersion.setToolTipText(C.i18n("ui.label.version")); // NOI18N
|
||||
|
||||
cboVersions.addItemListener(new java.awt.event.ItemListener() {
|
||||
public void itemStateChanged(java.awt.event.ItemEvent evt) {
|
||||
cboVersionsItemStateChanged(evt);
|
||||
}
|
||||
});
|
||||
|
||||
pnlPassword.setLayout(new java.awt.CardLayout());
|
||||
|
||||
lblPassword.setText(C.i18n("ui.label.password")); // NOI18N
|
||||
|
||||
txtPassword.addCaretListener(new javax.swing.event.CaretListener() {
|
||||
public void caretUpdate(javax.swing.event.CaretEvent evt) {
|
||||
txtPasswordCaretUpdate(evt);
|
||||
}
|
||||
});
|
||||
txtPassword.addFocusListener(new java.awt.event.FocusAdapter() {
|
||||
public void focusGained(java.awt.event.FocusEvent evt) {
|
||||
txtPasswordFocusGained(evt);
|
||||
}
|
||||
});
|
||||
txtPassword.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
txtPasswordActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
txtPassword.addKeyListener(new java.awt.event.KeyAdapter() {
|
||||
public void keyPressed(java.awt.event.KeyEvent evt) {
|
||||
txtPasswordKeyPressed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout pnlLogInLayout = new javax.swing.GroupLayout(pnlLogIn);
|
||||
pnlLogIn.setLayout(pnlLogInLayout);
|
||||
pnlLogInLayout.setHorizontalGroup(
|
||||
pnlLogInLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlLogInLayout.createSequentialGroup()
|
||||
.addComponent(lblPassword)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(txtPassword))
|
||||
);
|
||||
pnlLogInLayout.setVerticalGroup(
|
||||
pnlLogInLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnlLogInLayout.createSequentialGroup()
|
||||
.addGap(0, 0, Short.MAX_VALUE)
|
||||
.addGroup(pnlLogInLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 22, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)))
|
||||
);
|
||||
|
||||
pnlPassword.add(pnlLogIn, "card2");
|
||||
|
||||
btnLogout.setText(C.i18n("ui.button.logout")); // NOI18N
|
||||
btnLogout.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnLogoutActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout pnlLogOutLayout = new javax.swing.GroupLayout(pnlLogOut);
|
||||
pnlLogOut.setLayout(pnlLogOutLayout);
|
||||
pnlLogOutLayout.setHorizontalGroup(
|
||||
pnlLogOutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(btnLogout, javax.swing.GroupLayout.DEFAULT_SIZE, 170, Short.MAX_VALUE)
|
||||
);
|
||||
pnlLogOutLayout.setVerticalGroup(
|
||||
pnlLogOutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(btnLogout, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
);
|
||||
|
||||
pnlPassword.add(pnlLogOut, "card3");
|
||||
|
||||
btnImportModpack.setText(C.i18n("modpack.task.install")); // NOI18N
|
||||
btnImportModpack.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnImportModpackActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
btnExportModpack.setText(C.i18n("modpack.task.save")); // NOI18N
|
||||
btnExportModpack.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnExportModpackActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout pnlMoreLayout = new javax.swing.GroupLayout(pnlMore);
|
||||
pnlMore.setLayout(pnlMoreLayout);
|
||||
pnlMoreLayout.setHorizontalGroup(
|
||||
pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(pnlMoreLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(pnlPassword, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(pnlMoreLayout.createSequentialGroup()
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(lblProfile, javax.swing.GroupLayout.Alignment.TRAILING)
|
||||
.addComponent(lblVersion, javax.swing.GroupLayout.Alignment.TRAILING))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(cboProfiles, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(cboVersions, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
|
||||
.addGroup(pnlMoreLayout.createSequentialGroup()
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(lblUserName)
|
||||
.addComponent(lblLogin))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addComponent(cboLoginMode, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(txtPlayerName)))
|
||||
.addComponent(btnExportModpack, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addComponent(btnImportModpack, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addContainerGap())
|
||||
);
|
||||
pnlMoreLayout.setVerticalGroup(
|
||||
pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnlMoreLayout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblProfile)
|
||||
.addComponent(cboProfiles, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(cboVersions, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addComponent(lblVersion))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblLogin)
|
||||
.addComponent(cboLoginMode, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(pnlMoreLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(lblUserName)
|
||||
.addComponent(txtPlayerName, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(pnlPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 254, Short.MAX_VALUE)
|
||||
.addComponent(btnImportModpack)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnExportModpack)
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
pnlRoot.add(pnlMore);
|
||||
pnlMore.setBounds(0, 0, 190, 480);
|
||||
|
||||
add(pnlRoot);
|
||||
pnlRoot.setBounds(0, 0, 800, 480);
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void txtPlayerNameFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtPlayerNameFocusGained
|
||||
MainFrame.INSTANCE.closeMessage();
|
||||
}//GEN-LAST:event_txtPlayerNameFocusGained
|
||||
|
||||
private void txtPlayerNameFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtPlayerNameFocusLost
|
||||
IAuthenticator l = Settings.getInstance().getAuthenticator();
|
||||
l.setUserName(txtPlayerName.getText());
|
||||
}//GEN-LAST:event_txtPlayerNameFocusLost
|
||||
|
||||
private void cboLoginModeItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboLoginModeItemStateChanged
|
||||
if (evt.getStateChange() != ItemEvent.SELECTED)
|
||||
return;
|
||||
if (preparingAuth)
|
||||
return;
|
||||
Settings.getInstance().setLoginType(cboLoginMode.getSelectedIndex());
|
||||
}//GEN-LAST:event_cboLoginModeItemStateChanged
|
||||
|
||||
private void cboProfilesItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboProfilesItemStateChanged
|
||||
if (!isLoading && cboProfiles.getSelectedIndex() != -1 && !StrUtils.isBlank((String) cboProfiles.getSelectedItem()))
|
||||
Settings.getInstance().setLast((String) cboProfiles.getSelectedItem());
|
||||
}//GEN-LAST:event_cboProfilesItemStateChanged
|
||||
|
||||
private void cboVersionsItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboVersionsItemStateChanged
|
||||
if (isLoading || evt.getStateChange() != ItemEvent.SELECTED || cboVersions.getSelectedIndex() < 0 || StrUtils.isBlank((String) cboVersions.getSelectedItem()))
|
||||
return;
|
||||
String mcv = (String) cboVersions.getSelectedItem();
|
||||
Settings.getLastProfile().setSelectedVersion(mcv);
|
||||
}//GEN-LAST:event_cboVersionsItemStateChanged
|
||||
|
||||
private void txtPasswordFocusGained(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtPasswordFocusGained
|
||||
MainFrame.INSTANCE.closeMessage();
|
||||
}//GEN-LAST:event_txtPasswordFocusGained
|
||||
|
||||
private void txtPasswordActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtPasswordActionPerformed
|
||||
MainFrame.INSTANCE.daemon.runGame(Settings.getLastProfile());
|
||||
}//GEN-LAST:event_txtPasswordActionPerformed
|
||||
|
||||
private void btnLogoutActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLogoutActionPerformed
|
||||
if (preparingAuth)
|
||||
return;
|
||||
|
||||
IAuthenticator l = Settings.getInstance().getAuthenticator();
|
||||
CardLayout cl = (CardLayout) pnlPassword.getLayout();
|
||||
if (l.isLoggedIn())
|
||||
l.logOut();
|
||||
cl.first(pnlPassword);
|
||||
SwingUtilities.invokeLater(this::repaint);
|
||||
}//GEN-LAST:event_btnLogoutActionPerformed
|
||||
|
||||
private void txtPlayerNameKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtPlayerNameKeyPressed
|
||||
if (evt.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
IAuthenticator l = Settings.getInstance().getAuthenticator();
|
||||
l.setUserName(txtPlayerName.getText());
|
||||
if (!l.hasPassword())
|
||||
MainFrame.INSTANCE.daemon.runGame(Settings.getLastProfile());
|
||||
else if (!l.isLoggedIn())
|
||||
txtPassword.requestFocus();
|
||||
}
|
||||
}//GEN-LAST:event_txtPlayerNameKeyPressed
|
||||
|
||||
private void txtPasswordKeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtPasswordKeyPressed
|
||||
if (evt.getKeyCode() == KeyEvent.VK_ENTER)
|
||||
MainFrame.INSTANCE.daemon.runGame(Settings.getLastProfile());
|
||||
}//GEN-LAST:event_txtPasswordKeyPressed
|
||||
|
||||
private void btnImportModpackActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnImportModpackActionPerformed
|
||||
JSystemFileChooser fc = new JSystemFileChooser();
|
||||
fc.setFileSelectionMode(JSystemFileChooser.FILES_ONLY);
|
||||
fc.setDialogTitle(C.i18n("modpack.choose"));
|
||||
fc.setMultiSelectionEnabled(false);
|
||||
fc.setFileFilter(new FileNameExtensionFilter(C.i18n("modpack"), "zip"));
|
||||
fc.showOpenDialog(this);
|
||||
if (fc.getSelectedFile() == null)
|
||||
return;
|
||||
String suggestedModpackId = MessageBox.showInputDialog(C.i18n("modpack.enter_name"), FileUtils.getBaseName(fc.getSelectedFile().getName()));
|
||||
if (StrUtils.isBlank(suggestedModpackId))
|
||||
return;
|
||||
TaskWindow.factory().append(ModpackManager.install(MainFrame.INSTANCE, fc.getSelectedFile(), Settings.getLastProfile().service(), suggestedModpackId)).execute();
|
||||
Settings.getLastProfile().service().version().refreshVersions();
|
||||
}//GEN-LAST:event_btnImportModpackActionPerformed
|
||||
|
||||
private void btnExportModpackActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExportModpackActionPerformed
|
||||
if (Settings.getLastProfile().service().version().getVersionCount() <= 0)
|
||||
return;
|
||||
WizardDisplayer.showWizard(new ModpackWizard(Settings.getLastProfile()).createWizard());
|
||||
}//GEN-LAST:event_btnExportModpackActionPerformed
|
||||
|
||||
private void txtPasswordCaretUpdate(javax.swing.event.CaretEvent evt) {//GEN-FIRST:event_txtPasswordCaretUpdate
|
||||
Settings.getInstance().getAuthenticator().setPassword(txtPassword.getText());
|
||||
}//GEN-LAST:event_txtPasswordCaretUpdate
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="Loads">
|
||||
private void prepareAuths() {
|
||||
preparingAuth = true;
|
||||
cboLoginMode.removeAllItems();
|
||||
for (IAuthenticator str : AbstractAuthenticator.LOGINS)
|
||||
cboLoginMode.addItem(str.getName());
|
||||
preparingAuth = false;
|
||||
int loginType = Settings.getInstance().getLoginType();
|
||||
if (0 <= loginType && loginType < cboLoginMode.getItemCount()) {
|
||||
cboLoginMode.setSelectedIndex(loginType);
|
||||
Settings.getInstance().setLoginType(loginType);
|
||||
}
|
||||
}
|
||||
//</editor-fold>
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="Private Variables">
|
||||
boolean preparingAuth = true;
|
||||
private boolean isLoading = false;
|
||||
private javax.swing.JPanel pnlButtons;
|
||||
private CustomButton btnRun;
|
||||
private static final int DEFAULT_WIDTH = 800, DEFAULT_HEIGHT = 480;
|
||||
//</editor-fold>
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
initGui();
|
||||
super.onCreate();
|
||||
Settings.onProfileLoading();
|
||||
}
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton btnExportModpack;
|
||||
private javax.swing.JButton btnImportModpack;
|
||||
private javax.swing.JButton btnLogout;
|
||||
private javax.swing.JComboBox cboLoginMode;
|
||||
private javax.swing.JComboBox cboProfiles;
|
||||
private javax.swing.JComboBox cboVersions;
|
||||
private javax.swing.JLabel lblLogin;
|
||||
private javax.swing.JLabel lblPassword;
|
||||
private javax.swing.JLabel lblProfile;
|
||||
private javax.swing.JLabel lblUserName;
|
||||
private javax.swing.JLabel lblVersion;
|
||||
private javax.swing.JPanel pnlLogIn;
|
||||
private javax.swing.JPanel pnlLogOut;
|
||||
private javax.swing.JPanel pnlMore;
|
||||
private javax.swing.JPanel pnlPassword;
|
||||
private javax.swing.JPanel pnlRoot;
|
||||
private javax.swing.JPasswordField txtPassword;
|
||||
private javax.swing.JTextField txtPlayerName;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
|
||||
final Consumer<AuthenticatorChangedEvent> onAuthChanged = (x) -> {
|
||||
if (x.getValue().hasPassword()) {
|
||||
pnlPassword.setVisible(true);
|
||||
lblUserName.setText(C.i18n("login.account"));
|
||||
} else {
|
||||
pnlPassword.setVisible(false);
|
||||
lblUserName.setText(C.i18n("login.username"));
|
||||
}
|
||||
|
||||
CardLayout cl = (CardLayout) pnlPassword.getLayout();
|
||||
if (x.getValue().isLoggedIn())
|
||||
cl.last(pnlPassword);
|
||||
else
|
||||
cl.first(pnlPassword);
|
||||
String username = x.getValue().getUserName();
|
||||
if (username == null)
|
||||
username = "";
|
||||
txtPlayerName.setText(username);
|
||||
};
|
||||
|
||||
final Runnable onLoadingProfiles = () -> {
|
||||
isLoading = true;
|
||||
DefaultComboBoxModel<String> model = new DefaultComboBoxModel<>();
|
||||
for (Profile s : Settings.getProfilesFiltered())
|
||||
model.addElement(s.getName());
|
||||
cboProfiles.setModel(model);
|
||||
isLoading = false;
|
||||
};
|
||||
|
||||
final Consumer<RefreshedVersionsEvent> onRefreshedVersions = t -> {
|
||||
if (Settings.getLastProfile().service().version() == t.getSource())
|
||||
loadVersions();
|
||||
};
|
||||
|
||||
boolean showedNoVersion = false;
|
||||
|
||||
void loadVersions() {
|
||||
isLoading = true;
|
||||
cboVersions.removeAllItems();
|
||||
String selVersion = Settings.getLastProfile().getSelectedVersion();
|
||||
if (!Settings.getLastProfile().service().version().getVersions().isEmpty()) {
|
||||
for (MinecraftVersion mcVersion : Settings.getLastProfile().service().version().getVersions()) {
|
||||
if (mcVersion.hidden)
|
||||
continue;
|
||||
cboVersions.addItem(mcVersion.id);
|
||||
}
|
||||
versionChanged(selVersion);
|
||||
}
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
final Consumer<LaunchingStateChangedEvent> launchingStateChanged = t -> SwingUtilities.invokeLater(() -> SwingUtils.setEnabled(MainFrame.INSTANCE.getRootPane(), t.getValue() == LaunchingState.Done));
|
||||
|
||||
void versionChanged(String selectedVersion) {
|
||||
isLoading = true;
|
||||
DefaultComboBoxModel<String> model = (DefaultComboBoxModel<String>) cboVersions.getModel();
|
||||
for (int i = 0; i < model.getSize(); ++i)
|
||||
if (model.getElementAt(i).equals(selectedVersion)) {
|
||||
model.setSelectedItem(selectedVersion);
|
||||
break;
|
||||
}
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
final Consumer<ProfileChangedEvent> onSelectedProfilesChanged = event -> {
|
||||
Profile t = event.getValue();
|
||||
t.propertyChanged.register(e -> {
|
||||
if ("selectedVersion".equals(e.getPropertyName()))
|
||||
versionChanged(e.getNewValue());
|
||||
});
|
||||
|
||||
isLoading = true;
|
||||
DefaultComboBoxModel<String> model = (DefaultComboBoxModel<String>) cboProfiles.getModel();
|
||||
for (int i = 0; i < model.getSize(); ++i)
|
||||
if (model.getElementAt(i).equals(t.getName())) {
|
||||
model.setSelectedItem(t.getName());
|
||||
break;
|
||||
}
|
||||
isLoading = false;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<Form version="1.3" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JDialogFormInfo">
|
||||
<Properties>
|
||||
<Property name="defaultCloseOperation" type="int" value="2"/>
|
||||
<Property name="title" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.newProfileWindow.title" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<SyntheticProperties>
|
||||
<SyntheticProperty name="formSizePolicy" type="int" value="1"/>
|
||||
<SyntheticProperty name="generateCenter" type="boolean" value="false"/>
|
||||
</SyntheticProperties>
|
||||
<AuxValues>
|
||||
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
|
||||
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
|
||||
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
|
||||
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
|
||||
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
|
||||
</AuxValues>
|
||||
|
||||
<Layout>
|
||||
<DimensionLayout dim="0">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jLabel1" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="txtNewProfileName" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<Component id="jLabel2" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="cboProfiles" max="32767" attributes="0"/>
|
||||
</Group>
|
||||
<Group type="102" alignment="1" attributes="0">
|
||||
<EmptySpace min="0" pref="262" max="32767" attributes="0"/>
|
||||
<Component id="btnOK" min="-2" max="-2" attributes="0"/>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Component id="btnCancel" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
<DimensionLayout dim="1">
|
||||
<Group type="103" groupAlignment="0" attributes="0">
|
||||
<Group type="102" alignment="0" attributes="0">
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel1" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="txtNewProfileName" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="jLabel2" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="cboProfiles" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="32767" attributes="0"/>
|
||||
<Group type="103" groupAlignment="3" attributes="0">
|
||||
<Component id="btnCancel" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
<Component id="btnOK" alignment="3" min="-2" max="-2" attributes="0"/>
|
||||
</Group>
|
||||
<EmptySpace max="-2" attributes="0"/>
|
||||
</Group>
|
||||
</Group>
|
||||
</DimensionLayout>
|
||||
</Layout>
|
||||
<SubComponents>
|
||||
<Component class="javax.swing.JLabel" name="jLabel1">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.label.newProfileWindow.new_profile_name" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JTextField" name="txtNewProfileName">
|
||||
<Events>
|
||||
<EventHandler event="keyTyped" listener="java.awt.event.KeyListener" parameters="java.awt.event.KeyEvent" handler="txtNewProfileNameKeyTyped"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JLabel" name="jLabel2">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.label.newProfileWindow.copy_from" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JComboBox" name="cboProfiles">
|
||||
<Properties>
|
||||
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
|
||||
<StringArray count="0"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnCancel">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="button.cancel" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnCancelActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
<Component class="javax.swing.JButton" name="btnOK">
|
||||
<Properties>
|
||||
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
|
||||
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="ui.button.ok" replaceFormat="C.i18n("{key}")"/>
|
||||
</Property>
|
||||
</Properties>
|
||||
<Events>
|
||||
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnOKActionPerformed"/>
|
||||
</Events>
|
||||
</Component>
|
||||
</SubComponents>
|
||||
</Form>
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 org.jackhuang.hmcl.util.C;
|
||||
import org.jackhuang.hmcl.setting.Profile;
|
||||
import org.jackhuang.hmcl.setting.Settings;
|
||||
import org.jackhuang.hmcl.util.StrUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public final class NewProfileWindow extends javax.swing.JDialog {
|
||||
|
||||
public NewProfileWindow(java.awt.Frame parent) {
|
||||
super(parent, true);
|
||||
initComponents();
|
||||
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
for (Profile s : Settings.getProfilesFiltered())
|
||||
cboProfiles.addItem(s.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called from within the constructor to initialize the form.
|
||||
* WARNING: Do NOT modify this code. The content of this method is always
|
||||
* regenerated by the Form Editor.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
|
||||
private void initComponents() {
|
||||
|
||||
jLabel1 = new javax.swing.JLabel();
|
||||
txtNewProfileName = new javax.swing.JTextField();
|
||||
jLabel2 = new javax.swing.JLabel();
|
||||
cboProfiles = new javax.swing.JComboBox();
|
||||
btnCancel = new javax.swing.JButton();
|
||||
btnOK = new javax.swing.JButton();
|
||||
|
||||
setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
|
||||
setTitle(C.i18n("ui.newProfileWindow.title")); // NOI18N
|
||||
|
||||
jLabel1.setText(C.i18n("ui.label.newProfileWindow.new_profile_name")); // NOI18N
|
||||
|
||||
txtNewProfileName.addKeyListener(new java.awt.event.KeyAdapter() {
|
||||
public void keyTyped(java.awt.event.KeyEvent evt) {
|
||||
txtNewProfileNameKeyTyped(evt);
|
||||
}
|
||||
});
|
||||
|
||||
jLabel2.setText(C.i18n("ui.label.newProfileWindow.copy_from")); // NOI18N
|
||||
|
||||
btnCancel.setText(C.i18n("button.cancel")); // NOI18N
|
||||
btnCancel.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnCancelActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
btnOK.setText(C.i18n("ui.button.ok")); // NOI18N
|
||||
btnOK.addActionListener(new java.awt.event.ActionListener() {
|
||||
public void actionPerformed(java.awt.event.ActionEvent evt) {
|
||||
btnOKActionPerformed(evt);
|
||||
}
|
||||
});
|
||||
|
||||
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
|
||||
getContentPane().setLayout(layout);
|
||||
layout.setHorizontalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel1)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(txtNewProfileName))
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addComponent(jLabel2)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(cboProfiles, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
|
||||
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
|
||||
.addGap(0, 262, Short.MAX_VALUE)
|
||||
.addComponent(btnOK)
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addComponent(btnCancel)))
|
||||
.addContainerGap())
|
||||
);
|
||||
layout.setVerticalGroup(
|
||||
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
|
||||
.addGroup(layout.createSequentialGroup()
|
||||
.addContainerGap()
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel1)
|
||||
.addComponent(txtNewProfileName, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(jLabel2)
|
||||
.addComponent(cboProfiles, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
|
||||
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
|
||||
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
|
||||
.addComponent(btnCancel)
|
||||
.addComponent(btnOK))
|
||||
.addContainerGap())
|
||||
);
|
||||
|
||||
pack();
|
||||
}// </editor-fold>//GEN-END:initComponents
|
||||
|
||||
private void txtNewProfileNameKeyTyped(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_txtNewProfileNameKeyTyped
|
||||
switch (evt.getKeyCode()) {
|
||||
case 13:
|
||||
act();
|
||||
break;
|
||||
case 27:
|
||||
this.dispose();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}//GEN-LAST:event_txtNewProfileNameKeyTyped
|
||||
|
||||
private void btnOKActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOKActionPerformed
|
||||
act();
|
||||
}//GEN-LAST:event_btnOKActionPerformed
|
||||
|
||||
void act() {
|
||||
if (!StrUtils.isBlank(txtNewProfileName.getText())) {
|
||||
Profile newProfile = new Profile(txtNewProfileName.getText(), Settings.getProfile(cboProfiles.getSelectedItem().toString()));
|
||||
Settings.putProfile(newProfile);
|
||||
}
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
private void btnCancelActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCancelActionPerformed
|
||||
this.dispose();
|
||||
}//GEN-LAST:event_btnCancelActionPerformed
|
||||
|
||||
// Variables declaration - do not modify//GEN-BEGIN:variables
|
||||
private javax.swing.JButton btnCancel;
|
||||
private javax.swing.JButton btnOK;
|
||||
private javax.swing.JComboBox cboProfiles;
|
||||
private javax.swing.JLabel jLabel1;
|
||||
private javax.swing.JLabel jLabel2;
|
||||
private javax.swing.JTextField txtNewProfileName;
|
||||
// End of variables declaration//GEN-END:variables
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 javax.swing.JTabbedPane;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import org.jackhuang.hmcl.api.ui.TopTabPage;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class NewTabPane extends JTabbedPane implements ChangeListener {
|
||||
|
||||
public NewTabPane() {
|
||||
super();
|
||||
addChangeListener(this);
|
||||
}
|
||||
|
||||
public boolean initializing;
|
||||
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
if (initializing)
|
||||
return;
|
||||
for (int i = 0; i < getComponentCount(); ++i)
|
||||
if (getComponent(i) instanceof TopTabPage) {
|
||||
TopTabPage comp = (TopTabPage) getComponent(i);
|
||||
comp.setId(i);
|
||||
if (getSelectedIndex() != i)
|
||||
((TopTabPage) getComponent(i)).onLeave();
|
||||
}
|
||||
if (getSelectedComponent() instanceof TopTabPage) {
|
||||
if (page == null && getComponentCount() > 0 && getComponent(0) instanceof TopTabPage)
|
||||
page = (TopTabPage) getComponent(0);
|
||||
((TopTabPage) getSelectedComponent()).onSelect(page);
|
||||
page = (TopTabPage) getSelectedComponent();
|
||||
}
|
||||
}
|
||||
|
||||
TopTabPage page = null;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2013 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 org.jackhuang.hmcl.util.ui.Page;
|
||||
import org.jackhuang.hmcl.util.ui.IRepaint;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.Window;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import javax.swing.JComponent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author huang
|
||||
*/
|
||||
public class RepaintPage extends Page implements IRepaint {
|
||||
|
||||
public RepaintPage() {
|
||||
super();
|
||||
}
|
||||
|
||||
JComponent repainter;
|
||||
|
||||
@Override
|
||||
public JComponent getRepaintComponent() {
|
||||
return repainter;
|
||||
}
|
||||
|
||||
public void setRepainter(JComponent repainter) {
|
||||
this.repainter = repainter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Rectangle> getRepaintRects() {
|
||||
return Arrays.asList(this.getBounds());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window getRepaintWindow() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Group;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.SVGPath;
|
||||
|
||||
public final class SVG {
|
||||
private SVG() {
|
||||
}
|
||||
|
||||
private static Node createSVGPath(String d, ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
SVGPath path = new SVGPath();
|
||||
path.getStyleClass().add("svg");
|
||||
path.setContent(d);
|
||||
path.fillProperty().bind(fill);
|
||||
|
||||
if (width < 0 || height < 0) {
|
||||
StackPane pane = new StackPane(path);
|
||||
pane.setAlignment(Pos.CENTER);
|
||||
return pane;
|
||||
}
|
||||
|
||||
Group svg = new Group(path);
|
||||
double scale = Math.min(width / svg.getBoundsInParent().getWidth(), height / svg.getBoundsInParent().getHeight());
|
||||
svg.setScaleX(scale);
|
||||
svg.setScaleY(scale);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
||||
// default fill: white, width: 20, height 20
|
||||
|
||||
public static Node gear(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node back(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node close(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node dotsVertical(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node delete(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node accountEdit(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node expand(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node collapse(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node navigate(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node launch(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.14311.429 0 20.571 6.286z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node launch2(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node script(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node pencil(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node refresh(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node folderOpen(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node update(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node closeCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node checkCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node infoCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node helpCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node alert(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node plus(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node importIcon(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node export(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node openInNew(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node triangle(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M1,21H23L12,2", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node home(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node viewList(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z", fill, width, height);
|
||||
}
|
||||
|
||||
public static Node check(ObjectBinding<? extends Paint> fill, double width, double height) {
|
||||
return createSVGPath("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z", fill, width, height);
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.JFXColorPicker;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.When;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.text.Font;
|
||||
import org.jackhuang.hmcl.Metadata;
|
||||
import org.jackhuang.hmcl.setting.*;
|
||||
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
|
||||
import org.jackhuang.hmcl.ui.construct.Validator;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.upgrade.RemoteVersion;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChannel;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateChecker;
|
||||
import org.jackhuang.hmcl.upgrade.UpdateHandler;
|
||||
import org.jackhuang.hmcl.util.Logging;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales;
|
||||
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.net.Proxy;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.reservedSelectedPropertyFor;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
|
||||
|
||||
public final class SettingsPage extends SettingsView implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("settings.launcher"));
|
||||
|
||||
private InvalidationListener updateListener;
|
||||
|
||||
public SettingsPage() {
|
||||
FXUtils.smoothScrolling(scroll);
|
||||
|
||||
// ==== Download sources ====
|
||||
cboDownloadSource.getItems().setAll(DownloadProviders.providersById.keySet());
|
||||
selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty());
|
||||
// ====
|
||||
|
||||
// ==== Font ====
|
||||
cboFont.valueProperty().bindBidirectional(config().fontFamilyProperty());
|
||||
|
||||
txtFontSize.textProperty().bindBidirectional(config().fontSizeProperty(),
|
||||
SafeStringConverter.fromFiniteDouble()
|
||||
.restrict(it -> it > 0)
|
||||
.fallbackTo(12.0)
|
||||
.asPredicate(Validator.addTo(txtFontSize)));
|
||||
|
||||
lblDisplay.fontProperty().bind(Bindings.createObjectBinding(
|
||||
() -> Font.font(config().getFontFamily(), config().getFontSize()),
|
||||
config().fontFamilyProperty(), config().fontSizeProperty()));
|
||||
// ====
|
||||
|
||||
// ==== Languages ====
|
||||
cboLanguage.getItems().setAll(Locales.LOCALES);
|
||||
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
|
||||
// ====
|
||||
|
||||
// ==== Proxy ====
|
||||
txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty());
|
||||
txtProxyPort.textProperty().bindBidirectional(config().proxyPortProperty(),
|
||||
SafeStringConverter.fromInteger()
|
||||
.restrict(it -> it >= 0 && it <= 0xFFFF)
|
||||
.fallbackTo(0)
|
||||
.asPredicate(Validator.addTo(txtProxyPort)));
|
||||
txtProxyUsername.textProperty().bindBidirectional(config().proxyUserProperty());
|
||||
txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());
|
||||
|
||||
proxyPane.disableProperty().bind(chkDisableProxy.selectedProperty());
|
||||
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
|
||||
|
||||
reservedSelectedPropertyFor(chkDisableProxy).bindBidirectional(config().hasProxyProperty());
|
||||
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
|
||||
|
||||
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
|
||||
chkProxyHttp.setUserData(Proxy.Type.HTTP);
|
||||
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
|
||||
chkProxySocks.setUserData(Proxy.Type.SOCKS);
|
||||
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
|
||||
selectedItemPropertyFor(proxyConfigurationGroup, Proxy.Type.class).bindBidirectional(config().proxyTypeProperty());
|
||||
// ====
|
||||
|
||||
fileCommonLocation.loadChildren(Arrays.asList(
|
||||
fileCommonLocation.createChildren(i18n("launcher.cache_directory.default"), EnumCommonDirectory.DEFAULT)
|
||||
), EnumCommonDirectory.CUSTOM);
|
||||
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
|
||||
fileCommonLocation.customTextProperty().bindBidirectional(config().commonDirectoryProperty());
|
||||
fileCommonLocation.subtitleProperty().bind(
|
||||
Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())
|
||||
.orElse(i18n("launcher.cache_directory.disabled")),
|
||||
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
|
||||
|
||||
// ==== Update ====
|
||||
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
|
||||
updateListener = any -> {
|
||||
btnUpdate.setVisible(UpdateChecker.isOutdated());
|
||||
|
||||
if (UpdateChecker.isOutdated()) {
|
||||
lblUpdateSub.setText(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
|
||||
lblUpdateSub.getStyleClass().setAll("update-label");
|
||||
|
||||
lblUpdate.setText(i18n("update.found"));
|
||||
lblUpdate.getStyleClass().setAll("update-label");
|
||||
} else if (UpdateChecker.isCheckingUpdate()) {
|
||||
lblUpdateSub.setText(i18n("update.checking"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll();
|
||||
} else {
|
||||
lblUpdateSub.setText(i18n("update.latest"));
|
||||
lblUpdateSub.getStyleClass().setAll("subtitle-label");
|
||||
|
||||
lblUpdate.setText(i18n("update"));
|
||||
lblUpdate.getStyleClass().setAll();
|
||||
}
|
||||
};
|
||||
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
|
||||
updateListener.invalidated(null);
|
||||
|
||||
lblUpdateNote.setWrappingWidth(470);
|
||||
|
||||
ToggleGroup updateChannelGroup = new ToggleGroup();
|
||||
chkUpdateDev.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
|
||||
chkUpdateStable.setToggleGroup(updateChannelGroup);
|
||||
chkUpdateStable.setUserData(UpdateChannel.STABLE);
|
||||
selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class).bindBidirectional(config().updateChannelProperty());
|
||||
// ====
|
||||
|
||||
// ==== Background ====
|
||||
backgroundItem.loadChildren(Collections.singletonList(
|
||||
backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
|
||||
), EnumBackgroundImage.CUSTOM);
|
||||
backgroundItem.customTextProperty().bindBidirectional(config().backgroundImageProperty());
|
||||
backgroundItem.selectedDataProperty().bindBidirectional(config().backgroundImageTypeProperty());
|
||||
backgroundItem.subtitleProperty().bind(
|
||||
new When(backgroundItem.selectedDataProperty().isEqualTo(EnumBackgroundImage.DEFAULT))
|
||||
.then(i18n("launcher.background.default"))
|
||||
.otherwise(config().backgroundImageProperty()));
|
||||
// ====
|
||||
|
||||
// ==== Theme ====
|
||||
JFXColorPicker picker = new JFXColorPicker(Color.web(config().getTheme().getColor()), null);
|
||||
picker.setCustomColorText(i18n("color.custom"));
|
||||
picker.setRecentColorsText(i18n("color.recent"));
|
||||
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
|
||||
picker.setOnAction(e -> {
|
||||
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
|
||||
config().setTheme(theme);
|
||||
Controllers.getScene().getStylesheets().setAll(theme.getStylesheets());
|
||||
});
|
||||
themeColorPickerContainer.getChildren().setAll(picker);
|
||||
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
|
||||
// ====
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdate() {
|
||||
RemoteVersion target = UpdateChecker.getLatestVersion();
|
||||
if (target == null) {
|
||||
return;
|
||||
}
|
||||
UpdateHandler.updateFrom(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExportLogs() {
|
||||
// We cannot determine which file is JUL using.
|
||||
// So we write all the logs to a new file.
|
||||
thread(() -> {
|
||||
Path logFile = Paths.get("hmcl-exported-logs-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath();
|
||||
|
||||
LOG.info("Exporting logs to " + logFile);
|
||||
try {
|
||||
Files.write(logFile, Logging.getRawLogs());
|
||||
} catch (IOException e) {
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + e, null, MessageType.ERROR));
|
||||
LOG.log(Level.WARNING, "Failed to export logs", e);
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", logFile)));
|
||||
if (Desktop.isDesktopSupported()) {
|
||||
try {
|
||||
Desktop.getDesktop().open(logFile.toFile());
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHelp() {
|
||||
FXUtils.openLink(Metadata.HELP_URL);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSponsor() {
|
||||
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
|
||||
}
|
||||
}
|
||||
@@ -1,509 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import com.jfoenix.controls.*;
|
||||
import javafx.geometry.HPos;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.geometry.VPos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.*;
|
||||
import javafx.scene.text.Text;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
|
||||
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public abstract class SettingsView extends StackPane {
|
||||
protected final JFXTextField txtProxyHost;
|
||||
protected final JFXTextField txtProxyPort;
|
||||
protected final JFXTextField txtProxyUsername;
|
||||
protected final JFXPasswordField txtProxyPassword;
|
||||
protected final JFXTextField txtFontSize;
|
||||
protected final JFXComboBox<SupportedLocale> cboLanguage;
|
||||
protected final JFXComboBox<String> cboDownloadSource;
|
||||
protected final FontComboBox cboFont;
|
||||
protected final MultiFileItem<EnumCommonDirectory> fileCommonLocation;
|
||||
protected final Label lblDisplay;
|
||||
protected final Label lblUpdate;
|
||||
protected final Label lblUpdateSub;
|
||||
protected final Text lblUpdateNote;
|
||||
protected final JFXRadioButton chkUpdateStable;
|
||||
protected final JFXRadioButton chkUpdateDev;
|
||||
protected final JFXButton btnUpdate;
|
||||
protected final ScrollPane scroll;
|
||||
protected final MultiFileItem<EnumBackgroundImage> backgroundItem;
|
||||
protected final StackPane themeColorPickerContainer;
|
||||
protected final JFXCheckBox chkDisableProxy;
|
||||
protected final JFXRadioButton chkProxyHttp;
|
||||
protected final JFXRadioButton chkProxySocks;
|
||||
protected final JFXCheckBox chkProxyAuthentication;
|
||||
protected final GridPane authPane;
|
||||
protected final Pane proxyPane;
|
||||
|
||||
public SettingsView() {
|
||||
scroll = new ScrollPane();
|
||||
getChildren().setAll(scroll);
|
||||
scroll.setStyle("-fx-font-size: 14;");
|
||||
scroll.setFitToWidth(true);
|
||||
|
||||
{
|
||||
VBox rootPane = new VBox();
|
||||
rootPane.setStyle("-fx-padding: 18;");
|
||||
{
|
||||
ComponentList settingsPane = new ComponentList();
|
||||
{
|
||||
{
|
||||
StackPane sponsorPane = new StackPane();
|
||||
sponsorPane.setCursor(Cursor.HAND);
|
||||
sponsorPane.setOnMouseClicked(e -> onSponsor());
|
||||
{
|
||||
Label label = new Label(i18n("sponsor.hmcl"));
|
||||
label.setWrapText(true);
|
||||
label.prefWidthProperty().bind(sponsorPane.widthProperty());
|
||||
sponsorPane.getChildren().add(label);
|
||||
}
|
||||
settingsPane.getContent().add(sponsorPane);
|
||||
}
|
||||
|
||||
ComponentSublist updatePane = new ComponentSublist();
|
||||
updatePane.setTitle(i18n("update"));
|
||||
updatePane.setHasSubtitle(true);
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
lblUpdate = new Label(i18n("update"));
|
||||
lblUpdateSub = new Label();
|
||||
lblUpdateSub.getStyleClass().add("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);
|
||||
updatePane.setHeaderLeft(headerLeft);
|
||||
}
|
||||
|
||||
{
|
||||
btnUpdate = new JFXButton();
|
||||
btnUpdate.setOnMouseClicked(e -> onUpdate());
|
||||
btnUpdate.getStyleClass().add("toggle-icon4");
|
||||
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 20, 20));
|
||||
|
||||
updatePane.setHeaderRight(btnUpdate);
|
||||
}
|
||||
|
||||
{
|
||||
VBox content = new VBox();
|
||||
content.setSpacing(8);
|
||||
|
||||
chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
|
||||
chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
|
||||
|
||||
VBox noteWrapper = new VBox();
|
||||
noteWrapper.setStyle("-fx-padding: 10 0 0 0;");
|
||||
lblUpdateNote = new Text(i18n("update.note"));
|
||||
noteWrapper.getChildren().setAll(lblUpdateNote);
|
||||
|
||||
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
|
||||
|
||||
updatePane.getContent().add(content);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane updatePane = new BorderPane();
|
||||
{
|
||||
VBox headerLeft = new VBox();
|
||||
|
||||
Label help = new Label(i18n("help"));
|
||||
Label helpSubtitle = new Label(i18n("help.detail"));
|
||||
helpSubtitle.getStyleClass().add("subtitle-label");
|
||||
|
||||
headerLeft.getChildren().setAll(help, helpSubtitle);
|
||||
updatePane.setLeft(headerLeft);
|
||||
}
|
||||
|
||||
{
|
||||
JFXButton btnExternal = new JFXButton();
|
||||
btnExternal.setOnMouseClicked(e -> onHelp());
|
||||
btnExternal.getStyleClass().add("toggle-icon4");
|
||||
btnExternal.setGraphic(SVG.openInNew(Theme.blackFillBinding(), -1, -1));
|
||||
|
||||
updatePane.setRight(btnExternal);
|
||||
}
|
||||
settingsPane.getContent().add(updatePane);
|
||||
}
|
||||
|
||||
{
|
||||
fileCommonLocation = new MultiFileItem<>(true);
|
||||
fileCommonLocation.setTitle(i18n("launcher.cache_directory"));
|
||||
fileCommonLocation.setDirectory(true);
|
||||
fileCommonLocation.setChooserTitle(i18n("launcher.cache_directory.choose"));
|
||||
fileCommonLocation.setHasSubtitle(true);
|
||||
fileCommonLocation.setCustomText("settings.custom");
|
||||
|
||||
settingsPane.getContent().add(fileCommonLocation);
|
||||
}
|
||||
|
||||
{
|
||||
backgroundItem = new MultiFileItem<>(true);
|
||||
backgroundItem.setTitle(i18n("launcher.background"));
|
||||
backgroundItem.setChooserTitle(i18n("launcher.background.choose"));
|
||||
backgroundItem.setHasSubtitle(true);
|
||||
backgroundItem.setCustomText(i18n("settings.custom"));
|
||||
|
||||
settingsPane.getContent().add(backgroundItem);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane downloadSourcePane = new BorderPane();
|
||||
{
|
||||
Label label = new Label(i18n("settings.launcher.download_source"));
|
||||
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
|
||||
downloadSourcePane.setLeft(label);
|
||||
}
|
||||
|
||||
{
|
||||
cboDownloadSource = new JFXComboBox<>();
|
||||
cboDownloadSource.setConverter(stringConverter(key -> i18n("download.provider." + key)));
|
||||
downloadSourcePane.setRight(cboDownloadSource);
|
||||
}
|
||||
settingsPane.getContent().add(downloadSourcePane);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane languagePane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.language"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
languagePane.setLeft(left);
|
||||
|
||||
cboLanguage = new JFXComboBox<>();
|
||||
cboLanguage.setConverter(stringConverter(locale -> locale.getName(config().getLocalization().getResourceBundle())));
|
||||
FXUtils.setLimitWidth(cboLanguage, 400);
|
||||
languagePane.setRight(cboLanguage);
|
||||
|
||||
settingsPane.getContent().add(languagePane);
|
||||
}
|
||||
|
||||
{
|
||||
ComponentList proxyList = new ComponentList();
|
||||
proxyList.setTitle(i18n("settings.launcher.proxy"));
|
||||
|
||||
VBox proxyWrapper = new VBox();
|
||||
proxyWrapper.setSpacing(10);
|
||||
|
||||
{
|
||||
chkDisableProxy = new JFXCheckBox(i18n("settings.launcher.proxy.disable"));
|
||||
proxyWrapper.getChildren().add(chkDisableProxy);
|
||||
}
|
||||
|
||||
{
|
||||
proxyPane = new VBox();
|
||||
proxyPane.setStyle("-fx-padding: 0 0 0 30;");
|
||||
|
||||
ColumnConstraints colHgrow = new ColumnConstraints();
|
||||
colHgrow.setHgrow(Priority.ALWAYS);
|
||||
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
chkProxyHttp = new JFXRadioButton(i18n("settings.launcher.proxy.http"));
|
||||
chkProxySocks = new JFXRadioButton(i18n("settings.launcher.proxy.socks"));
|
||||
hBox.getChildren().setAll(chkProxyHttp, chkProxySocks);
|
||||
proxyPane.getChildren().add(hBox);
|
||||
}
|
||||
|
||||
{
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setHgap(20);
|
||||
gridPane.setVgap(10);
|
||||
gridPane.setStyle("-fx-padding: 0 0 0 15;");
|
||||
gridPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
|
||||
gridPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
|
||||
|
||||
{
|
||||
Label host = new Label(i18n("settings.launcher.proxy.host"));
|
||||
GridPane.setRowIndex(host, 1);
|
||||
GridPane.setColumnIndex(host, 0);
|
||||
GridPane.setHalignment(host, HPos.RIGHT);
|
||||
gridPane.getChildren().add(host);
|
||||
}
|
||||
|
||||
{
|
||||
txtProxyHost = new JFXTextField();
|
||||
GridPane.setRowIndex(txtProxyHost, 1);
|
||||
GridPane.setColumnIndex(txtProxyHost, 1);
|
||||
gridPane.getChildren().add(txtProxyHost);
|
||||
}
|
||||
|
||||
{
|
||||
Label port = new Label(i18n("settings.launcher.proxy.port"));
|
||||
GridPane.setRowIndex(port, 2);
|
||||
GridPane.setColumnIndex(port, 0);
|
||||
GridPane.setHalignment(port, HPos.RIGHT);
|
||||
gridPane.getChildren().add(port);
|
||||
}
|
||||
|
||||
{
|
||||
txtProxyPort = new JFXTextField();
|
||||
GridPane.setRowIndex(txtProxyPort, 2);
|
||||
GridPane.setColumnIndex(txtProxyPort, 1);
|
||||
FXUtils.setValidateWhileTextChanged(txtProxyPort, true);
|
||||
txtProxyHost.getValidators().setAll(new NumberValidator(i18n("input.number"), false));
|
||||
gridPane.getChildren().add(txtProxyPort);
|
||||
}
|
||||
proxyPane.getChildren().add(gridPane);
|
||||
}
|
||||
|
||||
{
|
||||
VBox vBox = new VBox();
|
||||
vBox.setStyle("-fx-padding: 20 0 20 5;");
|
||||
|
||||
chkProxyAuthentication = new JFXCheckBox(i18n("settings.launcher.proxy.authentication"));
|
||||
vBox.getChildren().setAll(chkProxyAuthentication);
|
||||
|
||||
proxyPane.getChildren().add(vBox);
|
||||
}
|
||||
|
||||
{
|
||||
authPane = new GridPane();
|
||||
authPane.setHgap(20);
|
||||
authPane.setVgap(10);
|
||||
authPane.setStyle("-fx-padding: 0 0 0 15;");
|
||||
authPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
|
||||
authPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
|
||||
|
||||
{
|
||||
Label username = new Label(i18n("settings.launcher.proxy.username"));
|
||||
GridPane.setRowIndex(username, 0);
|
||||
GridPane.setColumnIndex(username, 0);
|
||||
authPane.getChildren().add(username);
|
||||
}
|
||||
|
||||
{
|
||||
txtProxyUsername = new JFXTextField();
|
||||
GridPane.setRowIndex(txtProxyUsername, 0);
|
||||
GridPane.setColumnIndex(txtProxyUsername, 1);
|
||||
authPane.getChildren().add(txtProxyUsername);
|
||||
}
|
||||
|
||||
{
|
||||
Label password = new Label(i18n("settings.launcher.proxy.password"));
|
||||
GridPane.setRowIndex(password, 1);
|
||||
GridPane.setColumnIndex(password, 0);
|
||||
authPane.getChildren().add(password);
|
||||
}
|
||||
|
||||
{
|
||||
txtProxyPassword = new JFXPasswordField();
|
||||
GridPane.setRowIndex(txtProxyPassword, 1);
|
||||
GridPane.setColumnIndex(txtProxyPassword, 1);
|
||||
authPane.getChildren().add(txtProxyPassword);
|
||||
}
|
||||
|
||||
proxyPane.getChildren().add(authPane);
|
||||
}
|
||||
proxyWrapper.getChildren().add(proxyPane);
|
||||
}
|
||||
proxyList.getContent().add(proxyWrapper);
|
||||
settingsPane.getContent().add(proxyList);
|
||||
}
|
||||
|
||||
{
|
||||
BorderPane themePane = new BorderPane();
|
||||
|
||||
Label left = new Label(i18n("settings.launcher.theme"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
themePane.setLeft(left);
|
||||
|
||||
themeColorPickerContainer = new StackPane();
|
||||
themeColorPickerContainer.setMinHeight(30);
|
||||
themePane.setRight(themeColorPickerContainer);
|
||||
|
||||
settingsPane.getContent().add(themePane);
|
||||
}
|
||||
|
||||
{
|
||||
ComponentSublist logPane = new ComponentSublist();
|
||||
logPane.setTitle(i18n("settings.launcher.log"));
|
||||
|
||||
{
|
||||
JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export"));
|
||||
logButton.setOnMouseClicked(e -> onExportLogs());
|
||||
logButton.getStyleClass().add("jfx-button-border");
|
||||
|
||||
logPane.setHeaderRight(logButton);
|
||||
}
|
||||
|
||||
{
|
||||
VBox fontPane = new VBox();
|
||||
fontPane.setSpacing(5);
|
||||
|
||||
{
|
||||
BorderPane borderPane = new BorderPane();
|
||||
fontPane.getChildren().add(borderPane);
|
||||
{
|
||||
Label left = new Label(i18n("settings.launcher.log.font"));
|
||||
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
|
||||
borderPane.setLeft(left);
|
||||
}
|
||||
|
||||
{
|
||||
HBox hBox = new HBox();
|
||||
hBox.setSpacing(3);
|
||||
|
||||
cboFont = new FontComboBox(12);
|
||||
txtFontSize = new JFXTextField();
|
||||
FXUtils.setLimitWidth(txtFontSize, 50);
|
||||
hBox.getChildren().setAll(cboFont, txtFontSize);
|
||||
|
||||
borderPane.setRight(hBox);
|
||||
}
|
||||
}
|
||||
|
||||
lblDisplay = new Label("[23:33:33] [Client Thread/INFO] [WaterPower]: Loaded mod WaterPower.");
|
||||
fontPane.getChildren().add(lblDisplay);
|
||||
|
||||
logPane.getContent().add(fontPane);
|
||||
}
|
||||
settingsPane.getContent().add(logPane);
|
||||
}
|
||||
|
||||
{
|
||||
StackPane aboutPane = new StackPane();
|
||||
GridPane gridPane = new GridPane();
|
||||
gridPane.setHgap(20);
|
||||
gridPane.setVgap(10);
|
||||
|
||||
ColumnConstraints col1 = new ColumnConstraints();
|
||||
col1.setHgrow(Priority.SOMETIMES);
|
||||
col1.setMaxWidth(Double.NEGATIVE_INFINITY);
|
||||
col1.setMinWidth(Double.NEGATIVE_INFINITY);
|
||||
|
||||
ColumnConstraints col2 = new ColumnConstraints();
|
||||
col2.setHgrow(Priority.SOMETIMES);
|
||||
col2.setMinWidth(20);
|
||||
col2.setMaxWidth(Double.POSITIVE_INFINITY);
|
||||
|
||||
gridPane.getColumnConstraints().setAll(col1, col2);
|
||||
|
||||
RowConstraints row = new RowConstraints();
|
||||
row.setMinHeight(Double.NEGATIVE_INFINITY);
|
||||
row.setValignment(VPos.TOP);
|
||||
row.setVgrow(Priority.SOMETIMES);
|
||||
gridPane.getRowConstraints().setAll(row, row, row, row, row, row);
|
||||
|
||||
{
|
||||
Label label = new Label(i18n("about.copyright"));
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.copyright.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 0);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.author"));
|
||||
GridPane.setRowIndex(label, 1);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.author.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 1);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.thanks_to"));
|
||||
GridPane.setRowIndex(label, 2);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.thanks_to.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 2);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.dependency"));
|
||||
GridPane.setRowIndex(label, 3);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.dependency.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 3);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.claim"));
|
||||
GridPane.setRowIndex(label, 4);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.claim.statement"));
|
||||
label.setWrapText(true);
|
||||
label.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
GridPane.setRowIndex(label, 4);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.open_source"));
|
||||
GridPane.setRowIndex(label, 5);
|
||||
GridPane.setColumnIndex(label, 0);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
{
|
||||
Label label = new Label(i18n("about.open_source.statement"));
|
||||
label.setWrapText(true);
|
||||
GridPane.setRowIndex(label, 5);
|
||||
GridPane.setColumnIndex(label, 1);
|
||||
gridPane.getChildren().add(label);
|
||||
}
|
||||
aboutPane.getChildren().setAll(gridPane);
|
||||
settingsPane.getContent().add(aboutPane);
|
||||
}
|
||||
rootPane.getChildren().add(settingsPane);
|
||||
}
|
||||
scroll.setContent(rootPane);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onUpdate();
|
||||
protected abstract void onHelp();
|
||||
protected abstract void onExportLogs();
|
||||
protected abstract void onSponsor();
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.beans.InvalidationListener;
|
||||
import javafx.beans.WeakInvalidationListener;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.WeakChangeListener;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.collections.WeakListChangeListener;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class WeakListenerHolder {
|
||||
private List<Object> refs = new LinkedList<>();
|
||||
|
||||
public WeakListenerHolder() {
|
||||
}
|
||||
|
||||
public WeakInvalidationListener weak(InvalidationListener listener) {
|
||||
refs.add(listener);
|
||||
return new WeakInvalidationListener(listener);
|
||||
}
|
||||
|
||||
public <T> WeakChangeListener<T> weak(ChangeListener<T> listener) {
|
||||
refs.add(listener);
|
||||
return new WeakChangeListener<>(listener);
|
||||
}
|
||||
|
||||
public <T> WeakListChangeListener<T> weak(ListChangeListener<T> listener) {
|
||||
refs.add(listener);
|
||||
return new WeakListChangeListener<>(listener);
|
||||
}
|
||||
|
||||
public void add(Object obj) {
|
||||
refs.add(obj);
|
||||
}
|
||||
|
||||
public boolean remove(Object obj) {
|
||||
return refs.remove(obj);
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui;
|
||||
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.web.WebView;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
|
||||
public class WebStage extends Stage {
|
||||
private final WebView webView = new WebView();
|
||||
|
||||
public WebStage() {
|
||||
setScene(new Scene(webView, 800, 480));
|
||||
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
|
||||
getIcons().add(new Image("/assets/img/icon.png"));
|
||||
}
|
||||
|
||||
public WebView getWebView() {
|
||||
return webView;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AccountAdvancedListItem extends AdvancedListItem {
|
||||
private ObjectProperty<Account> account = new SimpleObjectProperty<Account>() {
|
||||
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
Account account = get();
|
||||
if (account == null) {
|
||||
titleProperty().unbind();
|
||||
setTitle(i18n("account.missing"));
|
||||
setSubtitle(i18n("account.missing.add"));
|
||||
imageProperty().unbind();
|
||||
setImage(new Image("/assets/img/craft_table.png"));
|
||||
} else {
|
||||
titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account));
|
||||
setSubtitle(accountSubtitle(account));
|
||||
imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public AccountAdvancedListItem() {
|
||||
setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1));
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> accountProperty() {
|
||||
return account;
|
||||
}
|
||||
|
||||
private static String accountSubtitle(Account account) {
|
||||
if (account instanceof OfflineAccount)
|
||||
return i18n("account.methods.offline");
|
||||
else if (account instanceof YggdrasilAccount)
|
||||
return account.getUsername();
|
||||
else
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.collections.FXCollections;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.ListPage;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
|
||||
|
||||
public class AccountList extends ListPage<AccountListItem> implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.manage"));
|
||||
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
|
||||
private final ObjectProperty<Account> selectedAccount;
|
||||
|
||||
public AccountList() {
|
||||
setItems(MappedObservableList.create(accounts, AccountListItem::new));
|
||||
selectedAccount = createSelectedItemPropertyFor(getItems(), Account.class);
|
||||
}
|
||||
|
||||
public ObjectProperty<Account> selectedAccountProperty() {
|
||||
return selectedAccount;
|
||||
}
|
||||
|
||||
public ListProperty<Account> accountsProperty() {
|
||||
return accounts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add() {
|
||||
Controllers.dialog(new AddAccountPane());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.*;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import org.jackhuang.hmcl.auth.Account;
|
||||
import org.jackhuang.hmcl.auth.AuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.CredentialExpiredException;
|
||||
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.game.TexturesLoader;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.ui.DialogController;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Lang.thread;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class AccountListItem extends RadioButton {
|
||||
|
||||
private final Account account;
|
||||
private final StringProperty title = new SimpleStringProperty();
|
||||
private final StringProperty subtitle = new SimpleStringProperty();
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
|
||||
|
||||
public AccountListItem(Account account) {
|
||||
this.account = account;
|
||||
getStyleClass().clear();
|
||||
setUserData(account);
|
||||
|
||||
String loginTypeName = Accounts.getLocalizedLoginTypeName(Accounts.getAccountFactory(account));
|
||||
if (account instanceof AuthlibInjectorAccount) {
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
|
||||
subtitle.bind(Bindings.concat(
|
||||
loginTypeName, ", ", i18n("account.injector.server"), ": ",
|
||||
Bindings.createStringBinding(server::getName, server)));
|
||||
} else {
|
||||
subtitle.set(loginTypeName);
|
||||
}
|
||||
|
||||
StringBinding characterName = Bindings.createStringBinding(account::getCharacter, account);
|
||||
if (account instanceof OfflineAccount) {
|
||||
title.bind(characterName);
|
||||
} else {
|
||||
title.bind(Bindings.concat(account.getUsername(), " - ", characterName));
|
||||
}
|
||||
|
||||
image.bind(TexturesLoader.fxAvatarBinding(account, 32));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new AccountListItemSkin(this);
|
||||
}
|
||||
|
||||
public void refresh() {
|
||||
account.clearCache();
|
||||
thread(() -> {
|
||||
try {
|
||||
account.logIn();
|
||||
} catch (CredentialExpiredException e) {
|
||||
try {
|
||||
DialogController.logIn(account);
|
||||
} catch (CancellationException e1) {
|
||||
// ignore cancellation
|
||||
} catch (Exception e1) {
|
||||
LOG.log(Level.WARNING, "Failed to refresh " + account + " with password", e1);
|
||||
}
|
||||
} catch (AuthenticationException e) {
|
||||
LOG.log(Level.WARNING, "Failed to refresh " + account + " with token", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
Accounts.getAccounts().remove(account);
|
||||
}
|
||||
|
||||
public Account getAccount() {
|
||||
return account;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image.get();
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXRadioButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
|
||||
public class AccountListItemSkin extends SkinBase<AccountListItem> {
|
||||
|
||||
public AccountListItemSkin(AccountListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
|
||||
JFXRadioButton chkSelected = new JFXRadioButton() {
|
||||
@Override
|
||||
public void fire() {
|
||||
skinnable.fire();
|
||||
}
|
||||
};
|
||||
BorderPane.setAlignment(chkSelected, Pos.CENTER);
|
||||
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
|
||||
root.setLeft(chkSelected);
|
||||
|
||||
HBox center = new HBox();
|
||||
center.setSpacing(8);
|
||||
center.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
|
||||
Label title = new Label();
|
||||
title.getStyleClass().add("title");
|
||||
title.textProperty().bind(skinnable.titleProperty());
|
||||
Label subtitle = new Label();
|
||||
subtitle.getStyleClass().add("subtitle");
|
||||
subtitle.textProperty().bind(skinnable.subtitleProperty());
|
||||
if (skinnable.getAccount() instanceof AuthlibInjectorAccount) {
|
||||
Tooltip tooltip = new Tooltip();
|
||||
AuthlibInjectorServer server = ((AuthlibInjectorAccount) skinnable.getAccount()).getServer();
|
||||
tooltip.textProperty().bind(BindingMapping.of(server, AuthlibInjectorServer::toString));
|
||||
FXUtils.installSlowTooltip(subtitle, tooltip);
|
||||
}
|
||||
VBox item = new VBox(title, subtitle);
|
||||
item.getStyleClass().add("two-line-list-item");
|
||||
BorderPane.setAlignment(item, Pos.CENTER);
|
||||
|
||||
center.getChildren().setAll(imageView, item);
|
||||
root.setCenter(center);
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
JFXButton btnRefresh = new JFXButton();
|
||||
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
|
||||
btnRefresh.getStyleClass().add("toggle-icon4");
|
||||
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), -1, -1));
|
||||
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")));
|
||||
right.getChildren().add(btnRefresh);
|
||||
|
||||
JFXButton btnRemove = new JFXButton();
|
||||
btnRemove.setOnMouseClicked(e -> skinnable.remove());
|
||||
btnRemove.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(btnRemove, Pos.CENTER);
|
||||
btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), -1, -1));
|
||||
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete")));
|
||||
right.getChildren().add(btnRemove);
|
||||
root.setRight(right);
|
||||
|
||||
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 12;");
|
||||
JFXDepthManager.setDepth(root, 1);
|
||||
|
||||
getChildren().setAll(root);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.controls.JFXPasswordField;
|
||||
import com.jfoenix.controls.JFXProgressBar;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
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.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
|
||||
public class AccountLoginPane extends StackPane {
|
||||
private final Account oldAccount;
|
||||
private final Consumer<AuthInfo> success;
|
||||
private final Runnable failed;
|
||||
|
||||
@FXML private Label lblUsername;
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private JFXProgressBar progressBar;
|
||||
|
||||
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
|
||||
this.oldAccount = oldAccount;
|
||||
this.success = success;
|
||||
this.failed = failed;
|
||||
|
||||
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
|
||||
|
||||
lblUsername.setText(oldAccount.getUsername());
|
||||
txtPassword.setOnAction(e -> onAccept());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAccept() {
|
||||
String password = txtPassword.getText();
|
||||
progressBar.setVisible(true);
|
||||
lblCreationWarning.setText("");
|
||||
Task.supplyAsync(() -> oldAccount.logInWithPassword(password))
|
||||
.whenComplete(Schedulers.javafx(), authInfo -> {
|
||||
success.accept(authInfo);
|
||||
fireEvent(new DialogCloseEvent());
|
||||
progressBar.setVisible(false);
|
||||
}, e -> {
|
||||
LOG.log(Level.INFO, "Failed to login with password: " + oldAccount, e);
|
||||
if (e instanceof NoSelectedCharacterException) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else {
|
||||
lblCreationWarning.setText(AddAccountPane.accountException(e));
|
||||
}
|
||||
progressBar.setVisible(false);
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCancel() {
|
||||
failed.run();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.utils.JFXUtilities;
|
||||
import com.jfoenix.controls.*;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ListProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleListProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Hyperlink;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.auth.*;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
|
||||
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
|
||||
import org.jackhuang.hmcl.game.TexturesLoader;
|
||||
import org.jackhuang.hmcl.setting.Accounts;
|
||||
import org.jackhuang.hmcl.task.Schedulers;
|
||||
import org.jackhuang.hmcl.task.Task;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.construct.*;
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.unmodifiableList;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.*;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AddAccountPane extends StackPane {
|
||||
|
||||
@FXML private JFXTextField txtUsername;
|
||||
@FXML private JFXPasswordField txtPassword;
|
||||
@FXML private Label lblCreationWarning;
|
||||
@FXML private Label lblPassword;
|
||||
@FXML private JFXComboBox<AccountFactory<?>> cboType;
|
||||
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers;
|
||||
@FXML private Label lblInjectorServer;
|
||||
@FXML private JFXButton btnAccept;
|
||||
@FXML private JFXButton btnAddServer;
|
||||
@FXML private JFXButton btnManageServer;
|
||||
@FXML private SpinnerPane acceptPane;
|
||||
@FXML private HBox linksContainer;
|
||||
|
||||
private ListProperty<Hyperlink> links = new SimpleListProperty<>();;
|
||||
|
||||
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));
|
||||
Bindings.bindContent(cboServers.getItems(), config().getAuthlibInjectorServers());
|
||||
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
|
||||
selectDefaultServer();
|
||||
|
||||
cboType.getItems().setAll(Accounts.FACTORY_OFFLINE, Accounts.FACTORY_MOJANG, Accounts.FACTORY_AUTHLIB_INJECTOR);
|
||||
cboType.setConverter(stringConverter(Accounts::getLocalizedLoginTypeName));
|
||||
// try selecting the preferred login type
|
||||
cboType.getSelectionModel().select(
|
||||
cboType.getItems().stream()
|
||||
.filter(type -> Accounts.getLoginType(type).equals(config().getPreferredLoginType()))
|
||||
.findFirst()
|
||||
.orElse(Accounts.FACTORY_OFFLINE));
|
||||
|
||||
btnAddServer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
btnManageServer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
|
||||
cboServers.getItems().addListener(onInvalidating(this::checkIfNoServer));
|
||||
checkIfNoServer();
|
||||
|
||||
ReadOnlyObjectProperty<AccountFactory<?>> loginType = cboType.getSelectionModel().selectedItemProperty();
|
||||
|
||||
// remember the last used login type
|
||||
loginType.addListener((observable, oldValue, newValue) -> config().setPreferredLoginType(Accounts.getLoginType(newValue)));
|
||||
|
||||
txtPassword.visibleProperty().bind(loginType.isNotEqualTo(Accounts.FACTORY_OFFLINE));
|
||||
lblPassword.visibleProperty().bind(txtPassword.visibleProperty());
|
||||
|
||||
cboServers.visibleProperty().bind(loginType.isEqualTo(Accounts.FACTORY_AUTHLIB_INJECTOR));
|
||||
lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
|
||||
txtUsername.getValidators().add(new Validator(i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
|
||||
|
||||
btnAccept.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> !( // consider the opposite situation: input is valid
|
||||
txtUsername.validate() &&
|
||||
// invisible means the field is not needed, neither should it be validated
|
||||
(!txtPassword.isVisible() || txtPassword.validate()) &&
|
||||
(!cboServers.isVisible() || cboServers.getSelectionModel().getSelectedItem() != null)
|
||||
),
|
||||
txtUsername.textProperty(),
|
||||
txtPassword.textProperty(), txtPassword.visibleProperty(),
|
||||
cboServers.getSelectionModel().selectedItemProperty(), cboServers.visibleProperty()));
|
||||
|
||||
// authlib-injector links
|
||||
links.bind(BindingMapping.of(cboServers.getSelectionModel().selectedItemProperty())
|
||||
.map(AddAccountPane::createHyperlinks)
|
||||
.map(FXCollections::observableList));
|
||||
Bindings.bindContent(linksContainer.getChildren(), links);
|
||||
linksContainer.visibleProperty().bind(cboServers.visibleProperty());
|
||||
}
|
||||
|
||||
private static final String[] ALLOWED_LINKS = { "register" };
|
||||
|
||||
public static List<Hyperlink> createHyperlinks(AuthlibInjectorServer server) {
|
||||
if (server == null) {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
Map<String, String> links = server.getLinks();
|
||||
List<Hyperlink> result = new ArrayList<>();
|
||||
for (String key : ALLOWED_LINKS) {
|
||||
String value = links.get(key);
|
||||
if (value != null) {
|
||||
Hyperlink link = new Hyperlink(i18n("account.injector.link." + key));
|
||||
FXUtils.installSlowTooltip(link, value);
|
||||
link.setOnAction(e -> FXUtils.openLink(value));
|
||||
result.add(link);
|
||||
}
|
||||
}
|
||||
return unmodifiableList(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the first server if no server is selected.
|
||||
*/
|
||||
private void selectDefaultServer() {
|
||||
if (!cboServers.getItems().isEmpty() && cboServers.getSelectionModel().isEmpty()) {
|
||||
cboServers.getSelectionModel().select(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkIfNoServer() {
|
||||
if (cboServers.getItems().isEmpty())
|
||||
cboServers.getStyleClass().setAll("jfx-combo-box-warning");
|
||||
else
|
||||
cboServers.getStyleClass().setAll("jfx-combo-box");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the additional data that needs to be passed into {@link AccountFactory#create(CharacterSelector, String, String, Object)}.
|
||||
*/
|
||||
private Object getAuthAdditionalData() {
|
||||
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
|
||||
if (factory == Accounts.FACTORY_AUTHLIB_INJECTOR) {
|
||||
return requireNonNull(cboServers.getSelectionModel().getSelectedItem(), "selected server cannot be null");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationAccept() {
|
||||
if (btnAccept.isDisabled())
|
||||
return;
|
||||
|
||||
acceptPane.showSpinner();
|
||||
lblCreationWarning.setText("");
|
||||
setDisable(true);
|
||||
|
||||
String username = txtUsername.getText();
|
||||
String password = txtPassword.getText();
|
||||
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
|
||||
Object additionalData = getAuthAdditionalData();
|
||||
|
||||
Task.supplyAsync(() -> factory.create(new Selector(), username, password, additionalData))
|
||||
.whenComplete(Schedulers.javafx(), account -> {
|
||||
int oldIndex = Accounts.getAccounts().indexOf(account);
|
||||
if (oldIndex == -1) {
|
||||
Accounts.getAccounts().add(account);
|
||||
} else {
|
||||
// adding an already-added account
|
||||
// instead of discarding the new account, we first remove the existing one then add the new one
|
||||
Accounts.getAccounts().remove(oldIndex);
|
||||
Accounts.getAccounts().add(oldIndex, account);
|
||||
}
|
||||
|
||||
// select the new account
|
||||
Accounts.setSelectedAccount(account);
|
||||
|
||||
acceptPane.hideSpinner();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}, exception -> {
|
||||
if (exception instanceof NoSelectedCharacterException) {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
} else {
|
||||
lblCreationWarning.setText(accountException(exception));
|
||||
}
|
||||
setDisable(false);
|
||||
acceptPane.hideSpinner();
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onCreationCancel() {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onManageInjecterServers() {
|
||||
fireEvent(new DialogCloseEvent());
|
||||
Controllers.navigate(Controllers.getServersPage());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddInjecterServer() {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
}
|
||||
|
||||
private class Selector extends BorderPane implements CharacterSelector {
|
||||
|
||||
private final AdvancedListBox listBox = new AdvancedListBox();
|
||||
private final JFXButton cancel = new JFXButton();
|
||||
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
private GameProfile selectedProfile = null;
|
||||
|
||||
public Selector() {
|
||||
setStyle("-fx-padding: 8px;");
|
||||
|
||||
cancel.setText(i18n("button.cancel"));
|
||||
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
|
||||
cancel.setOnMouseClicked(e -> latch.countDown());
|
||||
|
||||
listBox.startCategory(i18n("account.choose"));
|
||||
|
||||
setCenter(listBox);
|
||||
|
||||
HBox hbox = new HBox();
|
||||
hbox.setAlignment(Pos.CENTER_RIGHT);
|
||||
hbox.getChildren().add(cancel);
|
||||
setBottom(hbox);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameProfile select(YggdrasilService service, List<GameProfile> profiles) throws NoSelectedCharacterException {
|
||||
Platform.runLater(() -> {
|
||||
for (GameProfile profile : profiles) {
|
||||
ImageView portraitView = new ImageView();
|
||||
portraitView.setSmooth(false);
|
||||
portraitView.imageProperty().bind(TexturesLoader.fxAvatarBinding(service, profile.getId(), 32));
|
||||
FXUtils.limitSize(portraitView, 32, 32);
|
||||
|
||||
IconedItem accountItem = new IconedItem(portraitView, profile.getName());
|
||||
accountItem.setOnMouseClicked(e -> {
|
||||
selectedProfile = profile;
|
||||
latch.countDown();
|
||||
});
|
||||
listBox.add(accountItem);
|
||||
}
|
||||
Controllers.dialog(this);
|
||||
});
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
|
||||
if (selectedProfile == null)
|
||||
throw new NoSelectedCharacterException();
|
||||
|
||||
return selectedProfile;
|
||||
} catch (InterruptedException ignore) {
|
||||
throw new NoSelectedCharacterException();
|
||||
} finally {
|
||||
JFXUtilities.runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String accountException(Exception exception) {
|
||||
if (exception instanceof NoCharacterException) {
|
||||
return i18n("account.failed.no_character");
|
||||
} else if (exception instanceof ServerDisconnectException) {
|
||||
return i18n("account.failed.connect_authentication_server");
|
||||
} else if (exception instanceof ServerResponseMalformedException) {
|
||||
return i18n("account.failed.server_response_malformed");
|
||||
} 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 i18n("account.failed.invalid_credentials");
|
||||
else if (remoteMessage.contains("Invalid token"))
|
||||
return i18n("account.failed.invalid_token");
|
||||
else if (remoteMessage.contains("Invalid username or password"))
|
||||
return i18n("account.failed.invalid_password");
|
||||
}
|
||||
return exception.getMessage();
|
||||
} else if (exception instanceof AuthlibInjectorDownloadException) {
|
||||
return i18n("account.failed.injector_download_failure");
|
||||
} else if (exception instanceof CharacterDeletedException) {
|
||||
return i18n("account.failed.character_deleted");
|
||||
} else if (exception.getClass() == AuthenticationException.class) {
|
||||
return exception.getLocalizedMessage();
|
||||
} else {
|
||||
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXDialogLayout;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
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.DialogAware;
|
||||
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
|
||||
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
|
||||
import org.jackhuang.hmcl.util.io.NetworkUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
|
||||
import static org.jackhuang.hmcl.util.Logging.LOG;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class AddAuthlibInjectorServerPane extends StackPane implements DialogAware {
|
||||
|
||||
@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(txtServerUrl.textProperty().isEmpty());
|
||||
nextPane.hideSpinner();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDialogShown() {
|
||||
txtServerUrl.requestFocus();
|
||||
}
|
||||
|
||||
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 = txtServerUrl.getText();
|
||||
|
||||
nextPane.showSpinner();
|
||||
addServerPane.setDisable(true);
|
||||
|
||||
Task.runAsync(() -> {
|
||||
serverBeingAdded = AuthlibInjectorServer.locateServer(url);
|
||||
}).whenComplete(Schedulers.javafx(), exception -> {
|
||||
addServerPane.setDisable(false);
|
||||
nextPane.hideSpinner();
|
||||
|
||||
if (exception == null) {
|
||||
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 {
|
||||
LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception);
|
||||
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddPrev() {
|
||||
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAddFinish() {
|
||||
if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) {
|
||||
config().getAuthlibInjectorServers().add(serverBeingAdded);
|
||||
}
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.effects.JFXDepthManager;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public final class AuthlibInjectorServerItem extends BorderPane {
|
||||
private final AuthlibInjectorServer server;
|
||||
|
||||
private final Label lblServerName = new Label();
|
||||
private final Label lblServerUrl = new Label();
|
||||
|
||||
public AuthlibInjectorServerItem(AuthlibInjectorServer server, Consumer<AuthlibInjectorServerItem> deleteCallback) {
|
||||
this.server = server;
|
||||
|
||||
lblServerName.setStyle("-fx-font-size: 15;");
|
||||
lblServerUrl.setStyle("-fx-font-size: 10;");
|
||||
|
||||
VBox center = new VBox();
|
||||
BorderPane.setAlignment(center, Pos.CENTER);
|
||||
center.getChildren().addAll(lblServerName, lblServerUrl);
|
||||
setCenter(center);
|
||||
|
||||
JFXButton right = new JFXButton();
|
||||
right.setOnMouseClicked(e -> deleteCallback.accept(this));
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
BorderPane.setAlignment(right, Pos.CENTER);
|
||||
right.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
|
||||
setRight(right);
|
||||
|
||||
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
|
||||
JFXDepthManager.setDepth(this, 1);
|
||||
lblServerName.textProperty().bind(Bindings.createStringBinding(server::getName, server));
|
||||
lblServerUrl.setText(server.getUrl());
|
||||
}
|
||||
|
||||
public AuthlibInjectorServer getServer() {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.account;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.ReadOnlyStringProperty;
|
||||
import javafx.beans.property.ReadOnlyStringWrapper;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
|
||||
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;
|
||||
|
||||
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
|
||||
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.injector.manage.title"));
|
||||
|
||||
@FXML private ScrollPane scrollPane;
|
||||
@FXML private VBox listPane;
|
||||
@FXML private StackPane contentPane;
|
||||
|
||||
private ObservableList<AuthlibInjectorServerItem> serverItems;
|
||||
|
||||
public AuthlibInjectorServersPage() {
|
||||
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
|
||||
smoothScrolling(scrollPane);
|
||||
|
||||
serverItems = MappedObservableList.create(config().getAuthlibInjectorServers(), this::createServerItem);
|
||||
Bindings.bindContent(listPane.getChildren(), serverItems);
|
||||
}
|
||||
|
||||
private AuthlibInjectorServerItem createServerItem(AuthlibInjectorServer server) {
|
||||
return new AuthlibInjectorServerItem(server,
|
||||
item -> config().getAuthlibInjectorServers().remove(item.getServer()));
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void onAdd() {
|
||||
Controllers.dialog(new AddAuthlibInjectorServerPane());
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadOnlyStringProperty titleProperty() {
|
||||
return title.getReadOnlyProperty();
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public interface AnimationHandler {
|
||||
Duration getDuration();
|
||||
Pane getCurrentRoot();
|
||||
Node getPreviousNode();
|
||||
Node getCurrentNode();
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.animation;
|
||||
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.util.Duration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public enum ContainerAnimations {
|
||||
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 -> {
|
||||
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(0);
|
||||
}, c ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))),
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_IN(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 ->
|
||||
Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
|
||||
/**
|
||||
* A zoom effect
|
||||
*/
|
||||
ZOOM_OUT(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 ->
|
||||
(Arrays.asList(new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))))),
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_LEFT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
|
||||
}, 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)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
|
||||
|
||||
/**
|
||||
* A swipe effect
|
||||
*/
|
||||
SWIPE_RIGHT(c -> {
|
||||
c.getPreviousNode().setScaleX(1);
|
||||
c.getPreviousNode().setScaleY(1);
|
||||
c.getPreviousNode().setOpacity(0);
|
||||
c.getPreviousNode().setTranslateX(0);
|
||||
c.getCurrentNode().setScaleX(1);
|
||||
c.getCurrentNode().setScaleY(1);
|
||||
c.getCurrentNode().setOpacity(1);
|
||||
c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth());
|
||||
}, 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)),
|
||||
new KeyFrame(c.getDuration(),
|
||||
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
|
||||
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))));
|
||||
|
||||
private final 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() {
|
||||
return animationProducer;
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public final class TransitionHandler implements AnimationHandler {
|
||||
private final StackPane view;
|
||||
private Timeline animation;
|
||||
private Duration duration;
|
||||
private Node previousNode, currentNode;
|
||||
|
||||
/**
|
||||
* @param view A stack pane that contains another control that is {@link Parent}
|
||||
*/
|
||||
public TransitionHandler(StackPane view) {
|
||||
this.view = view;
|
||||
currentNode = view.getChildren().stream().findFirst().orElse(null);
|
||||
|
||||
// prevent content overflow
|
||||
Rectangle clip = new Rectangle();
|
||||
clip.widthProperty().bind(view.widthProperty());
|
||||
clip.heightProperty().bind(view.heightProperty());
|
||||
view.setClip(clip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getPreviousNode() {
|
||||
return previousNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getCurrentNode() {
|
||||
return currentNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StackPane getCurrentRoot() {
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
public void setContent(Node newView, AnimationProducer transition) {
|
||||
setContent(newView, transition, Duration.millis(320));
|
||||
}
|
||||
|
||||
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
|
||||
this.duration = duration;
|
||||
|
||||
Timeline prev = animation;
|
||||
if (prev != null)
|
||||
prev.stop();
|
||||
|
||||
updateContent(newView);
|
||||
|
||||
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 = EMPTY_PANE;
|
||||
} else
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
if (previousNode == newView)
|
||||
previousNode = EMPTY_PANE;
|
||||
|
||||
view.setMouseTransparent(true);
|
||||
|
||||
currentNode = newView;
|
||||
|
||||
view.getChildren().setAll(previousNode, currentNode);
|
||||
}
|
||||
|
||||
private final StackPane EMPTY_PANE = new StackPane();
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.layout.Pane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class AdvancedListBox extends ScrollPane {
|
||||
private final VBox container = new VBox();
|
||||
|
||||
{
|
||||
setContent(container);
|
||||
|
||||
FXUtils.smoothScrolling(this);
|
||||
|
||||
setFitToHeight(true);
|
||||
setFitToWidth(true);
|
||||
setHbarPolicy(ScrollBarPolicy.NEVER);
|
||||
|
||||
container.setSpacing(5);
|
||||
container.getStyleClass().add("advanced-list-box-content");
|
||||
}
|
||||
|
||||
public AdvancedListBox add(Node child) {
|
||||
if (child instanceof Pane || child instanceof AdvancedListItem)
|
||||
container.getChildren().add(child);
|
||||
else {
|
||||
StackPane pane = new StackPane();
|
||||
pane.getStyleClass().add("advanced-list-box-item");
|
||||
pane.getChildren().setAll(child);
|
||||
container.getChildren().add(pane);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedListBox remove(Node child) {
|
||||
if (child instanceof Pane)
|
||||
container.getChildren().remove(child);
|
||||
else {
|
||||
StackPane pane = null;
|
||||
for (Node node : container.getChildren())
|
||||
if (node instanceof StackPane) {
|
||||
ObservableList<Node> list = ((StackPane) node).getChildren();
|
||||
if (list.size() == 1 && list.get(0) == child)
|
||||
pane = (StackPane) node;
|
||||
}
|
||||
if (pane == null)
|
||||
throw new Error();
|
||||
container.getChildren().remove(pane);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public AdvancedListBox startCategory(String category) {
|
||||
return add(new ClassTitle(category));
|
||||
}
|
||||
|
||||
public void setSpacing(double spacing) {
|
||||
container.setSpacing(spacing);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
container.getChildren().clear();
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.property.*;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.Skin;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
|
||||
public class AdvancedListItem extends Control {
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
|
||||
private final ObjectProperty<Node> rightGraphic = new SimpleObjectProperty<>(this, "rightGraphic");
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
|
||||
private final BooleanProperty actionButtonVisible = new SimpleBooleanProperty(this, "actionButtonVisible", true);
|
||||
|
||||
public AdvancedListItem() {
|
||||
addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent()));
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public Node getRightGraphic() {
|
||||
return rightGraphic.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> rightGraphicProperty() {
|
||||
return rightGraphic;
|
||||
}
|
||||
|
||||
public void setRightGraphic(Node rightGraphic) {
|
||||
this.rightGraphic.set(rightGraphic);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public boolean isActionButtonVisible() {
|
||||
return actionButtonVisible.get();
|
||||
}
|
||||
|
||||
public BooleanProperty actionButtonVisibleProperty() {
|
||||
return actionButtonVisible;
|
||||
}
|
||||
|
||||
public void setActionButtonVisible(boolean actionButtonVisible) {
|
||||
this.actionButtonVisible.set(actionButtonVisible);
|
||||
}
|
||||
|
||||
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {
|
||||
return onAction;
|
||||
}
|
||||
|
||||
public final void setOnAction(EventHandler<ActionEvent> value) {
|
||||
onActionProperty().set(value);
|
||||
}
|
||||
|
||||
public final EventHandler<ActionEvent> getOnAction() {
|
||||
return onActionProperty().get();
|
||||
}
|
||||
|
||||
private ObjectProperty<EventHandler<ActionEvent>> onAction = new SimpleObjectProperty<EventHandler<ActionEvent>>(this, "onAction") {
|
||||
@Override
|
||||
protected void invalidated() {
|
||||
setEventHandler(ActionEvent.ACTION, get());
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected Skin<?> createDefaultSkin() {
|
||||
return new AdvancedListItemSkin(this);
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.text.TextAlignment;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
|
||||
|
||||
public AdvancedListItemSkin(AdvancedListItem skinnable) {
|
||||
super(skinnable);
|
||||
|
||||
StackPane stackPane = new StackPane();
|
||||
RipplerContainer container = new RipplerContainer(stackPane);
|
||||
|
||||
BorderPane root = new BorderPane();
|
||||
root.setPickOnBounds(false);
|
||||
|
||||
HBox left = new HBox();
|
||||
left.setAlignment(Pos.CENTER);
|
||||
left.setMouseTransparent(true);
|
||||
|
||||
StackPane imageViewContainer = new StackPane();
|
||||
FXUtils.setLimitWidth(imageViewContainer, 32);
|
||||
FXUtils.setLimitHeight(imageViewContainer, 32);
|
||||
|
||||
ImageView imageView = new ImageView();
|
||||
FXUtils.limitSize(imageView, 32, 32);
|
||||
imageView.setPreserveRatio(true);
|
||||
imageView.imageProperty().bind(skinnable.imageProperty());
|
||||
imageViewContainer.getChildren().setAll(imageView);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
vbox.setAlignment(Pos.CENTER_LEFT);
|
||||
vbox.setPadding(new Insets(0, 0, 0, 10));
|
||||
|
||||
Label title = new Label();
|
||||
title.textProperty().bind(skinnable.titleProperty());
|
||||
title.setMaxWidth(90);
|
||||
title.setStyle("-fx-font-size: 15;");
|
||||
title.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
vbox.getChildren().add(title);
|
||||
|
||||
Label subtitle = new Label();
|
||||
subtitle.textProperty().bind(skinnable.subtitleProperty());
|
||||
subtitle.setMaxWidth(90);
|
||||
subtitle.setStyle("-fx-font-size: 10;");
|
||||
subtitle.setTextAlignment(TextAlignment.JUSTIFY);
|
||||
vbox.getChildren().add(subtitle);
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.subtitleProperty(), subtitleString -> {
|
||||
if (subtitleString == null) vbox.getChildren().setAll(title);
|
||||
else vbox.getChildren().setAll(title, subtitle);
|
||||
});
|
||||
|
||||
left.getChildren().setAll(imageViewContainer, vbox);
|
||||
root.setLeft(left);
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setAlignment(Pos.CENTER);
|
||||
right.setMouseTransparent(true);
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
FXUtils.setLimitWidth(right, 40);
|
||||
FXUtils.onChangeAndOperate(skinnable.rightGraphicProperty(),
|
||||
newGraphic -> {
|
||||
if (newGraphic == null) {
|
||||
right.getChildren().clear();
|
||||
} else {
|
||||
right.getChildren().setAll(newGraphic);
|
||||
}
|
||||
});
|
||||
root.setRight(right);
|
||||
|
||||
FXUtils.onChangeAndOperate(skinnable.actionButtonVisibleProperty(),
|
||||
visible -> root.setRight(visible ? right : null));
|
||||
|
||||
stackPane.setStyle("-fx-padding: 10 16 10 16;");
|
||||
stackPane.getStyleClass().add("transparent");
|
||||
stackPane.setPickOnBounds(false);
|
||||
stackPane.getChildren().setAll(root);
|
||||
|
||||
getChildren().setAll(container);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.scene.text.Text;
|
||||
import org.jackhuang.hmcl.util.Lang;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
public class ClassTitle extends StackPane {
|
||||
private final Node content;
|
||||
|
||||
public ClassTitle(String text) {
|
||||
this(new Text(text));
|
||||
}
|
||||
|
||||
public ClassTitle(Node content) {
|
||||
this.content = content;
|
||||
|
||||
VBox vbox = new VBox();
|
||||
vbox.getChildren().addAll(content);
|
||||
Rectangle rectangle = new Rectangle();
|
||||
rectangle.widthProperty().bind(vbox.widthProperty());
|
||||
rectangle.setHeight(1.0);
|
||||
rectangle.setFill(Color.GRAY);
|
||||
vbox.getChildren().add(rectangle);
|
||||
getChildren().setAll(vbox);
|
||||
getStyleClass().add("class-title");
|
||||
}
|
||||
|
||||
public ClassTitle(String text, Node rightNode) {
|
||||
this(Lang.apply(new BorderPane(), borderPane -> {
|
||||
borderPane.setLeft(Lang.apply(new VBox(), vBox -> vBox.getChildren().setAll(new Text(text))));
|
||||
borderPane.setRight(rightNode);
|
||||
}));
|
||||
}
|
||||
|
||||
public Node getContent() {
|
||||
return content;
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
|
||||
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.IntegerProperty;
|
||||
import javafx.beans.property.SimpleIntegerProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Control;
|
||||
import javafx.scene.control.SkinBase;
|
||||
import javafx.scene.layout.VBox;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public class ComponentList extends Control {
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title", "Group");
|
||||
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle", "");
|
||||
private final IntegerProperty depth = new SimpleIntegerProperty(this, "depth", 0);
|
||||
private boolean hasSubtitle = false;
|
||||
public final ObservableList<Node> content = FXCollections.observableArrayList();
|
||||
|
||||
public ComponentList() {
|
||||
getStyleClass().add("options-list");
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getSubtitle() {
|
||||
return subtitle.get();
|
||||
}
|
||||
|
||||
public StringProperty subtitleProperty() {
|
||||
return subtitle;
|
||||
}
|
||||
|
||||
public void setSubtitle(String subtitle) {
|
||||
this.subtitle.set(subtitle);
|
||||
}
|
||||
|
||||
public int getDepth() {
|
||||
return depth.get();
|
||||
}
|
||||
|
||||
public IntegerProperty depthProperty() {
|
||||
return depth;
|
||||
}
|
||||
|
||||
public void setDepth(int depth) {
|
||||
this.depth.set(depth);
|
||||
}
|
||||
|
||||
public boolean isHasSubtitle() {
|
||||
return hasSubtitle;
|
||||
}
|
||||
|
||||
public void setHasSubtitle(boolean hasSubtitle) {
|
||||
this.hasSubtitle = hasSubtitle;
|
||||
}
|
||||
|
||||
public ObservableList<Node> getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected javafx.scene.control.Skin<?> createDefaultSkin() {
|
||||
return new Skin(this);
|
||||
}
|
||||
|
||||
protected static class Skin extends SkinBase<ComponentList> {
|
||||
private static final PseudoClass PSEUDO_CLASS_FIRST = PseudoClass.getPseudoClass("first");
|
||||
|
||||
private final ObservableList<Node> list;
|
||||
private final ObjectBinding<Node> firstItem;
|
||||
|
||||
protected Skin(ComponentList control) {
|
||||
super(control);
|
||||
|
||||
list = MappedObservableList.create(control.getContent(), node -> {
|
||||
ComponentListCell cell = new ComponentListCell(node);
|
||||
cell.getStyleClass().add("options-list-item");
|
||||
return cell;
|
||||
});
|
||||
|
||||
firstItem = Bindings.valueAt(list, 0);
|
||||
firstItem.addListener((observable, oldValue, newValue) -> {
|
||||
if (newValue != null)
|
||||
newValue.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, true);
|
||||
if (oldValue != null)
|
||||
oldValue.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, false);
|
||||
});
|
||||
if (!list.isEmpty())
|
||||
list.get(0).pseudoClassStateChanged(PSEUDO_CLASS_FIRST, true);
|
||||
|
||||
VBox vbox = new VBox();
|
||||
Bindings.bindContent(vbox.getChildren(), list);
|
||||
getChildren().setAll(vbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.animation.Animation;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.KeyValue;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.shape.Rectangle;
|
||||
import javafx.util.Duration;
|
||||
import org.jackhuang.hmcl.setting.Theme;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.ui.SVG;
|
||||
|
||||
/**
|
||||
* @author huangyuhui
|
||||
*/
|
||||
class ComponentListCell extends StackPane {
|
||||
private final Node content;
|
||||
private Animation expandAnimation;
|
||||
private Rectangle clipRect;
|
||||
private final BooleanProperty expanded = new SimpleBooleanProperty(this, "expanded", false);
|
||||
|
||||
ComponentListCell(Node content) {
|
||||
this.content = content;
|
||||
|
||||
updateLayout();
|
||||
}
|
||||
|
||||
private void updateClip(double newHeight) {
|
||||
if (clipRect != null)
|
||||
clipRect.setHeight(newHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void layoutChildren() {
|
||||
super.layoutChildren();
|
||||
|
||||
if (clipRect == null)
|
||||
clipRect = new Rectangle(0, 0, getWidth(), getHeight());
|
||||
else {
|
||||
clipRect.setX(0);
|
||||
clipRect.setY(0);
|
||||
clipRect.setHeight(getHeight());
|
||||
clipRect.setWidth(getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateLayout() {
|
||||
if (content instanceof ComponentList) {
|
||||
ComponentList list = (ComponentList) content;
|
||||
content.getStyleClass().remove("options-list");
|
||||
content.getStyleClass().add("options-sublist");
|
||||
|
||||
BorderPane groupNode = new BorderPane();
|
||||
|
||||
Node expandIcon = SVG.expand(Theme.blackFillBinding(), 10, 10);
|
||||
JFXButton expandButton = new JFXButton();
|
||||
expandButton.setGraphic(expandIcon);
|
||||
expandButton.getStyleClass().add("options-list-item-expand-button");
|
||||
|
||||
VBox labelVBox = new VBox();
|
||||
labelVBox.setAlignment(Pos.CENTER_LEFT);
|
||||
|
||||
boolean overrideHeaderLeft = false;
|
||||
if (list instanceof ComponentSublist) {
|
||||
Node leftNode = ((ComponentSublist) list).getHeaderLeft();
|
||||
if (leftNode != null) {
|
||||
labelVBox.getChildren().setAll(leftNode);
|
||||
overrideHeaderLeft = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!overrideHeaderLeft) {
|
||||
Label label = new Label();
|
||||
label.textProperty().bind(list.titleProperty());
|
||||
labelVBox.getChildren().add(label);
|
||||
|
||||
if (list.isHasSubtitle()) {
|
||||
Label subtitleLabel = new Label();
|
||||
subtitleLabel.textProperty().bind(list.subtitleProperty());
|
||||
subtitleLabel.getStyleClass().add("subtitle-label");
|
||||
labelVBox.getChildren().add(subtitleLabel);
|
||||
}
|
||||
}
|
||||
|
||||
groupNode.setLeft(labelVBox);
|
||||
|
||||
HBox right = new HBox();
|
||||
right.setSpacing(16);
|
||||
right.setAlignment(Pos.CENTER_RIGHT);
|
||||
if (list instanceof ComponentSublist) {
|
||||
Node rightNode = ((ComponentSublist) list).getHeaderRight();
|
||||
if (rightNode != null)
|
||||
right.getChildren().add(rightNode);
|
||||
}
|
||||
right.getChildren().add(expandButton);
|
||||
groupNode.setRight(right);
|
||||
|
||||
VBox container = new VBox();
|
||||
container.setPadding(new Insets(8, 0, 0, 0));
|
||||
FXUtils.setLimitHeight(container, 0);
|
||||
FXUtils.setOverflowHidden(container, true);
|
||||
container.getChildren().setAll(content);
|
||||
groupNode.setBottom(container);
|
||||
|
||||
expandButton.setOnMouseClicked(e -> {
|
||||
if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) {
|
||||
expandAnimation.stop();
|
||||
}
|
||||
|
||||
setExpanded(!isExpanded());
|
||||
|
||||
double newAnimatedHeight = content.prefHeight(-1) * (isExpanded() ? 1 : -1);
|
||||
double newHeight = isExpanded() ? getHeight() + newAnimatedHeight : prefHeight(-1);
|
||||
double contentHeight = isExpanded() ? newAnimatedHeight : 0;
|
||||
|
||||
if (isExpanded()) {
|
||||
updateClip(newHeight);
|
||||
}
|
||||
|
||||
expandAnimation = new Timeline(new KeyFrame(new Duration(320.0),
|
||||
new KeyValue(container.minHeightProperty(), contentHeight, FXUtils.SINE),
|
||||
new KeyValue(container.maxHeightProperty(), contentHeight, FXUtils.SINE)
|
||||
));
|
||||
|
||||
if (!isExpanded()) {
|
||||
expandAnimation.setOnFinished(e2 -> updateClip(newHeight));
|
||||
}
|
||||
|
||||
expandAnimation.play();
|
||||
});
|
||||
|
||||
expandedProperty().addListener((a, b, newValue) ->
|
||||
expandIcon.setRotate(newValue ? 180 : 0));
|
||||
|
||||
getChildren().setAll(groupNode);
|
||||
} else
|
||||
getChildren().setAll(content);
|
||||
}
|
||||
|
||||
public boolean isExpanded() {
|
||||
return expanded.get();
|
||||
}
|
||||
|
||||
public BooleanProperty expandedProperty() {
|
||||
return expanded;
|
||||
}
|
||||
|
||||
public void setExpanded(boolean expanded) {
|
||||
this.expanded.set(expanded);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.scene.Node;
|
||||
|
||||
@DefaultProperty("content")
|
||||
public final class ComponentSublist extends ComponentList {
|
||||
|
||||
private final ObjectProperty<Node> headerLeft = new SimpleObjectProperty<>(this, "headerLeft");
|
||||
private final ObjectProperty<Node> headerRight = new SimpleObjectProperty<>(this, "headerRight");
|
||||
|
||||
public ComponentSublist() {
|
||||
super();
|
||||
}
|
||||
|
||||
public Node getHeaderLeft() {
|
||||
return headerLeft.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> headerLeftProperty() {
|
||||
return headerLeft;
|
||||
}
|
||||
|
||||
public void setHeaderLeft(Node headerLeft) {
|
||||
this.headerLeft.set(headerLeft);
|
||||
}
|
||||
|
||||
public Node getHeaderRight() {
|
||||
return headerRight.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Node> headerRightProperty() {
|
||||
return headerRight;
|
||||
}
|
||||
|
||||
public void setHeaderRight(Node headerRight) {
|
||||
this.headerRight.set(headerRight);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.event.Event;
|
||||
import javafx.event.EventTarget;
|
||||
import javafx.event.EventType;
|
||||
import javafx.scene.layout.Region;
|
||||
import org.jackhuang.hmcl.ui.Controllers;
|
||||
|
||||
/**
|
||||
* 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<>("DIALOG_CLOSE");
|
||||
|
||||
public DialogCloseEvent() {
|
||||
super(CLOSE);
|
||||
}
|
||||
|
||||
public DialogCloseEvent(Object source, EventTarget target) {
|
||||
super(source, target, CLOSE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.stage.DirectoryChooser;
|
||||
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 java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
|
||||
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
|
||||
|
||||
public class FileItem extends BorderPane {
|
||||
private final Label lblPath = new Label();
|
||||
|
||||
private final SimpleStringProperty name = new SimpleStringProperty(this, "name");
|
||||
private final SimpleStringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final SimpleStringProperty tooltip = new SimpleStringProperty(this, "tooltip");
|
||||
private final SimpleStringProperty path = new SimpleStringProperty(this, "path");
|
||||
private final SimpleBooleanProperty convertToRelativePath = new SimpleBooleanProperty(this, "convertToRelativePath");
|
||||
|
||||
public FileItem() {
|
||||
VBox left = new VBox();
|
||||
Label name = new Label();
|
||||
name.textProperty().bind(nameProperty());
|
||||
lblPath.getStyleClass().addAll("subtitle-label");
|
||||
lblPath.textProperty().bind(path);
|
||||
left.getChildren().addAll(name, lblPath);
|
||||
setLeft(left);
|
||||
|
||||
JFXButton right = new JFXButton();
|
||||
right.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
|
||||
right.getStyleClass().add("toggle-icon4");
|
||||
right.setOnMouseClicked(e -> onExplore());
|
||||
FXUtils.installFastTooltip(right, i18n("button.edit"));
|
||||
setRight(right);
|
||||
|
||||
Tooltip tip = new Tooltip();
|
||||
tip.textProperty().bind(tooltipProperty());
|
||||
Tooltip.install(this, tip);
|
||||
|
||||
convertToRelativePath.addListener(onInvalidating(() -> path.set(processPath(path.get()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given path to absolute/relative(if possible) path according to {@link #convertToRelativePathProperty()}.
|
||||
*/
|
||||
private String processPath(String path) {
|
||||
Path given = Paths.get(path).toAbsolutePath();
|
||||
if (isConvertToRelativePath()) {
|
||||
try {
|
||||
return Paths.get(".").normalize().toAbsolutePath().relativize(given).normalize().toString();
|
||||
} catch (IllegalArgumentException e) {
|
||||
// the given path can't be relativized against current path
|
||||
}
|
||||
}
|
||||
return given.normalize().toString();
|
||||
}
|
||||
|
||||
public void onExplore() {
|
||||
DirectoryChooser chooser = new DirectoryChooser();
|
||||
if (path.get() != null) {
|
||||
File file = new File(path.get());
|
||||
if (file.exists()) {
|
||||
if (file.isFile())
|
||||
file = file.getAbsoluteFile().getParentFile();
|
||||
else if (file.isDirectory())
|
||||
file = file.getAbsoluteFile();
|
||||
chooser.setInitialDirectory(file);
|
||||
}
|
||||
}
|
||||
chooser.titleProperty().bind(titleProperty());
|
||||
File selectedDir = chooser.showDialog(Controllers.getStage());
|
||||
if (selectedDir != null) {
|
||||
path.set(processPath(selectedDir.toString()));
|
||||
}
|
||||
chooser.titleProperty().unbind();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name.get();
|
||||
}
|
||||
|
||||
public StringProperty nameProperty() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name.set(name);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public String getTooltip() {
|
||||
return tooltip.get();
|
||||
}
|
||||
|
||||
public StringProperty tooltipProperty() {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
public void setTooltip(String tooltip) {
|
||||
this.tooltip.set(tooltip);
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path.get();
|
||||
}
|
||||
|
||||
public StringProperty pathProperty() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path.set(path);
|
||||
}
|
||||
|
||||
public boolean isConvertToRelativePath() {
|
||||
return convertToRelativePath.get();
|
||||
}
|
||||
|
||||
public BooleanProperty convertToRelativePathProperty() {
|
||||
return convertToRelativePath;
|
||||
}
|
||||
|
||||
public void setConvertToRelativePath(boolean convertToRelativePath) {
|
||||
this.convertToRelativePath.set(convertToRelativePath);
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import static javafx.collections.FXCollections.emptyObservableList;
|
||||
import static javafx.collections.FXCollections.observableList;
|
||||
import static javafx.collections.FXCollections.singletonObservableList;
|
||||
|
||||
import org.jackhuang.hmcl.util.javafx.BindingMapping;
|
||||
|
||||
import com.jfoenix.controls.JFXComboBox;
|
||||
import com.jfoenix.controls.JFXListCell;
|
||||
|
||||
import javafx.beans.NamedArg;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.scene.text.Font;
|
||||
|
||||
public class FontComboBox extends JFXComboBox<String> {
|
||||
|
||||
private boolean loaded = false;
|
||||
|
||||
public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize) {
|
||||
styleProperty().bind(Bindings.concat("-fx-font-family: \"", valueProperty(), "\""));
|
||||
|
||||
setCellFactory(listView -> new JFXListCell<String>() {
|
||||
@Override
|
||||
public void updateItem(String item, boolean empty) {
|
||||
super.updateItem(item, empty);
|
||||
if (!empty) {
|
||||
setText(item);
|
||||
setGraphic(null);
|
||||
setStyle("-fx-font-family: \"" + item + "\"");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
itemsProperty().bind(BindingMapping.of(valueProperty())
|
||||
.map(value -> value == null ? emptyObservableList() : singletonObservableList(value)));
|
||||
|
||||
setOnMouseClicked(e -> {
|
||||
if (loaded)
|
||||
return;
|
||||
itemsProperty().unbind();
|
||||
setItems(observableList(Font.getFamilies()));
|
||||
loaded = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
|
||||
public class IconedItem extends RipplerContainer {
|
||||
|
||||
private Label label;
|
||||
|
||||
public IconedItem(Node icon, String text) {
|
||||
this(icon);
|
||||
label.setText(text);
|
||||
}
|
||||
|
||||
public IconedItem(Node icon) {
|
||||
super(createHBox(icon));
|
||||
label = ((Label) lookup("#label"));
|
||||
getStyleClass().setAll("iconed-item");
|
||||
}
|
||||
|
||||
private static HBox createHBox(Node icon) {
|
||||
HBox hBox = new HBox();
|
||||
|
||||
if (icon != null) {
|
||||
icon.setMouseTransparent(true);
|
||||
hBox.getChildren().add(icon);
|
||||
}
|
||||
|
||||
hBox.getStyleClass().add("iconed-item-container");
|
||||
Label textLabel = new Label();
|
||||
textLabel.setId("label");
|
||||
textLabel.setMouseTransparent(true);
|
||||
hBox.getChildren().addAll(textLabel);
|
||||
return hBox;
|
||||
}
|
||||
|
||||
public Label getLabel() {
|
||||
return label;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
|
||||
public class IconedMenuItem extends IconedItem {
|
||||
|
||||
public IconedMenuItem(Node node, String text, Runnable action) {
|
||||
super(node, text);
|
||||
|
||||
getStyleClass().setAll("iconed-menu-item");
|
||||
setOnMouseClicked(e -> action.run());
|
||||
}
|
||||
|
||||
public IconedMenuItem addTooltip(String tooltip) {
|
||||
FXUtils.installFastTooltip(this, tooltip);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import javafx.beans.DefaultProperty;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.beans.property.SimpleStringProperty;
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.VBox;
|
||||
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;
|
||||
|
||||
@DefaultProperty("image")
|
||||
public final class ImagePickerItem extends BorderPane {
|
||||
|
||||
private final ImageView imageView;
|
||||
|
||||
private final StringProperty title = new SimpleStringProperty(this, "title");
|
||||
private final ObjectProperty<EventHandler<? super MouseEvent>> onSelectButtonClicked = new SimpleObjectProperty<>(this, "onSelectButtonClicked");
|
||||
private final ObjectProperty<EventHandler<? super MouseEvent>> onDeleteButtonClicked = new SimpleObjectProperty<>(this, "onDeleteButtonClicked");
|
||||
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
|
||||
|
||||
public ImagePickerItem() {
|
||||
imageView = new ImageView();
|
||||
imageView.setSmooth(false);
|
||||
imageView.setPreserveRatio(true);
|
||||
|
||||
JFXButton selectButton = new JFXButton();
|
||||
selectButton.setGraphic(SVG.pencil(Theme.blackFillBinding(), 15, 15));
|
||||
selectButton.onMouseClickedProperty().bind(onSelectButtonClicked);
|
||||
selectButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
JFXButton deleteButton = new JFXButton();
|
||||
deleteButton.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
|
||||
deleteButton.onMouseClickedProperty().bind(onDeleteButtonClicked);
|
||||
deleteButton.getStyleClass().add("toggle-icon4");
|
||||
|
||||
FXUtils.installFastTooltip(selectButton, i18n("button.edit"));
|
||||
|
||||
HBox hBox = new HBox();
|
||||
hBox.getChildren().setAll(imageView, selectButton, deleteButton);
|
||||
hBox.setAlignment(Pos.CENTER_RIGHT);
|
||||
hBox.setSpacing(8);
|
||||
setRight(hBox);
|
||||
|
||||
VBox vBox = new VBox();
|
||||
Label label = new Label();
|
||||
label.textProperty().bind(title);
|
||||
vBox.getChildren().setAll(label);
|
||||
vBox.setAlignment(Pos.CENTER_LEFT);
|
||||
setLeft(vBox);
|
||||
|
||||
imageView.imageProperty().bind(image);
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title.get();
|
||||
}
|
||||
|
||||
public StringProperty titleProperty() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public void setTitle(String title) {
|
||||
this.title.set(title);
|
||||
}
|
||||
|
||||
public EventHandler<? super MouseEvent> getOnSelectButtonClicked() {
|
||||
return onSelectButtonClicked.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<? super MouseEvent>> onSelectButtonClickedProperty() {
|
||||
return onSelectButtonClicked;
|
||||
}
|
||||
|
||||
public void setOnSelectButtonClicked(EventHandler<? super MouseEvent> onSelectButtonClicked) {
|
||||
this.onSelectButtonClicked.set(onSelectButtonClicked);
|
||||
}
|
||||
|
||||
public EventHandler<? super MouseEvent> getOnDeleteButtonClicked() {
|
||||
return onDeleteButtonClicked.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<EventHandler<? super MouseEvent>> onDeleteButtonClickedProperty() {
|
||||
return onDeleteButtonClicked;
|
||||
}
|
||||
|
||||
public void setOnDeleteButtonClicked(EventHandler<? super MouseEvent> onDeleteButtonClicked) {
|
||||
this.onDeleteButtonClicked.set(onDeleteButtonClicked);
|
||||
}
|
||||
|
||||
public Image getImage() {
|
||||
return image.get();
|
||||
}
|
||||
|
||||
public ObjectProperty<Image> imageProperty() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public void setImage(Image image) {
|
||||
this.image.set(image);
|
||||
}
|
||||
|
||||
public ImageView getImageView() {
|
||||
return imageView;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher
|
||||
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.jackhuang.hmcl.ui.construct;
|
||||
|
||||
import com.jfoenix.controls.JFXButton;
|
||||
import com.jfoenix.controls.JFXTextField;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import org.jackhuang.hmcl.ui.FXUtils;
|
||||
import org.jackhuang.hmcl.util.FutureCallback;
|
||||
|
||||
public class InputDialogPane extends StackPane {
|
||||
|
||||
@FXML
|
||||
private JFXButton acceptButton;
|
||||
@FXML
|
||||
private JFXButton cancelButton;
|
||||
@FXML
|
||||
private JFXTextField textField;
|
||||
@FXML
|
||||
private Label content;
|
||||
@FXML
|
||||
private Label lblCreationWarning;
|
||||
@FXML
|
||||
private SpinnerPane acceptPane;
|
||||
|
||||
public InputDialogPane(String text, FutureCallback<String> onResult) {
|
||||
FXUtils.loadFXML(this, "/assets/fxml/input-dialog.fxml");
|
||||
content.setText(text);
|
||||
cancelButton.setOnMouseClicked(e -> fireEvent(new DialogCloseEvent()));
|
||||
acceptButton.setOnMouseClicked(e -> {
|
||||
acceptPane.showSpinner();
|
||||
onResult.call(textField.getText(), () -> {
|
||||
acceptPane.hideSpinner();
|
||||
fireEvent(new DialogCloseEvent());
|
||||
}, msg -> {
|
||||
acceptPane.hideSpinner();
|
||||
lblCreationWarning.setText(msg);
|
||||
});
|
||||
});
|
||||
|
||||
acceptButton.disableProperty().bind(Bindings.createBooleanBinding(
|
||||
() -> !textField.validate(),
|
||||
textField.textProperty()
|
||||
));
|
||||
}
|
||||
|
||||
public void setInitialText(String text) {
|
||||
textField.setText(text);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 992 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user