Compare commits

...
165 Commits
Author SHA1 Message Date
huanghongxun ab5bb8ea6c customized theme 2018-02-22 21:54:20 +08:00
huanghongxun d097b84c82 Fixed #285 2018-02-22 16:26:47 +08:00
huanghongxun f285635e76 Add multiple mods. Closes #287 2018-02-22 16:16:25 +08:00
huanghongxun 0f7b5299e9 Fixed incorrect format of userProperties. Closes #286 2018-02-22 15:46:48 +08:00
huanghongxun 7a788d1e6e cleanup 2018-02-22 15:02:12 +08:00
huanghongxun 6c4231a6b2 authentication reconstruction 2018-02-22 14:12:55 +08:00
huanghongxun 6e202341ad clean up 2018-02-20 18:03:34 +08:00
huanghongxun 95a509b86c ToStringBuilder 2018-02-20 12:11:31 +08:00
huanghongxun 602114af55 UI maintainence 2018-02-20 10:48:59 +08:00
huanghongxun e5ccc40358 clear cached skin records when refrshing account state 2018-02-18 18:35:05 +08:00
huanghongxun 2310f6e1c0 Fix http warning not being hidden if https used 2018-02-18 18:29:22 +08:00
huanghongxun 9ae952bb78 Add comments 2018-02-18 18:26:01 +08:00
huanghongxun 57a7eaccf5 Optimize imports 2018-02-18 17:16:42 +08:00
huanghongxun 1934bfa962 Merge pull request #282 from yushijinhun/javafx
重构
2018-02-18 17:12:16 +08:00
yushijinhun a5f3ec7b9b fix indents 2018-02-18 17:07:54 +08:00
yushijinhun 3cfcf1e772 用定义的类型代替FXUtils.addListener中的Pair 2018-02-18 16:47:00 +08:00
yushijinhun b45907a9f6 Polish, 处理部分警告 2018-02-18 16:23:12 +08:00
yushijinhun 8fd9395e23 删除对SSL的干涉(出于安全考虑) 2018-02-18 16:03:13 +08:00
yushijinhun 126b2a19e7 ReflectionHelper中使用参数化类型 2018-02-18 16:01:21 +08:00
yushijinhun bde01af89d help compilers to infer types
ecc cannot infer it
2018-02-18 15:49:11 +08:00
yushijinhun 65491ef18e Remove 3-arg Lang.merge() 2018-02-18 15:44:32 +08:00
huanghongxun 44a9470613 Fixed same skin among characters in one account 2018-02-18 15:42:18 +08:00
yushijinhun 470045f228 Rename Lang.getOrPut() to computeIfAbsent()
See Map.computeIfAbsent() in java9
2018-02-18 15:33:58 +08:00
yushijinhun d081a6ee92 Remove Lang.requireJsonNonNull(), use JsonUtils.fromNonNullJson() instead 2018-02-18 15:32:00 +08:00
yushijinhun 95f404bb64 move emptyConsumer() to Constants 2018-02-18 15:20:00 +08:00
yushijinhun dcea76bcd3 Add missing @SafeVarargs in Lang 2018-02-18 15:17:17 +08:00
yushijinhun e9b816fb87 Change Lang.EMPTY_CONSUMER to emptyConsumer() 2018-02-18 15:15:28 +08:00
huanghongxun 20a7039f59 GC after game launched 2018-02-18 13:54:13 +08:00
huanghongxun 4ad172dda4 authlib-injector prefetch support 2018-02-18 09:39:56 +08:00
huanghongxun adf6c19996 Cache session profile response. Closes #280 2018-02-17 23:38:40 +08:00
huanghongxun 24ec0adacf Authlib Injector support 2018-02-17 23:28:05 +08:00
huanghongxun 83bc6748a3 HMCLCore authlib injector support 2018-02-17 11:01:08 +08:00
huangyuhui bfed651263 hmcl.log in normal format 2018-02-15 01:29:19 +08:00
huangyuhui 8bd4f2b5b5 hmcl.log in normal format 2018-02-14 23:42:08 +08:00
huangyuhui 3d7bc0b593 Will not wait for launching if launching rd versions 2018-02-14 23:01:23 +08:00
huangyuhui 473c6f2b3c Additional information on main page 2018-02-14 22:54:28 +08:00
huangyuhui 484ce6c895 try javafx static attributes 2018-02-14 22:19:25 +08:00
huangyuhui eeccdbca72 proxy settings ui optimization 2018-02-14 20:57:48 +08:00
huangyuhui 552238676c Fixed activation of mods not working. Closes #259 2018-02-14 15:16:42 +08:00
huangyuhui c9e7bf7ca5 Yggdrasil account offline logging in 2018-02-14 14:51:42 +08:00
huangyuhui 8d1e335abf Downloading snapshot versions 2018-02-14 14:15:34 +08:00
huangyuhui c76bcb5d15 show retry link when failed to fetch version list 2018-02-14 13:29:08 +08:00
huangyuhui 7732b21ca2 add introduction to installing modpacks 2018-02-14 01:45:21 +08:00
huangyuhui 123d9b791c Unify the font of tab header 2018-02-14 01:19:29 +08:00
huangyuhui a237ca799f Fixed darker color of title bar 2018-02-14 00:34:50 +08:00
huangyuhui f850c2461a Current account tip 2018-02-14 00:19:25 +08:00
huangyuhui 0fb248e6be theme selection 2018-02-14 00:15:27 +08:00
huangyuhui 7fca697234 current directory for selected profile 2018-02-13 22:28:27 +08:00
huangyuhui 4004525d3a english i18n 2018-02-13 22:23:59 +08:00
huangyuhui 4f67788d4a modifiable theme color 2018-02-13 21:36:19 +08:00
huangyuhui 69ea41056d background customizing 2018-02-13 18:37:33 +08:00
huangyuhui 6443770670 update prompt 2018-02-13 12:16:53 +08:00
huangyuhui 46917af37c Renaming profile 2018-02-12 23:52:10 +08:00
huangyuhui 47a4a0eaf5 Add profile button move to category title 2018-02-12 23:23:39 +08:00
huangyuhui 03dbecd5ae About 2018-02-12 23:02:33 +08:00
huangyuhui f6c95e3095 Show alex or steve portrait for offline account 2018-02-12 21:25:47 +08:00
huangyuhui 6823291eb0 Ignoring IOExceptions in log4j handler 2018-02-08 00:04:02 +08:00
huangyuhui 98b43945ed Global and special version settings. Fixed property bindings 2018-02-07 22:33:52 +08:00
huangyuhui 8792c423d5 Disabling launcher checking game completion 2018-02-07 21:05:58 +08:00
huangyuhui 5607298898 reduce 'return null' 2018-02-07 21:01:09 +08:00
huangyuhui 35047477fe Fixed crash when a version inherits from a nonexistent version. Closes #269 2018-02-06 16:40:31 +08:00
huanghongxun 08a31a92f2 Merge pull request #267 from mslxl/javafx
Fix `Unable to move temp file`, `File::renameTo` is problematic.
2018-02-06 09:54:08 +08:00
huangyuhui 6e2fc7ff97 Maintains minecraft arguments when installing or uninstalling Forge, OptiFine, LiteLoader 2018-02-06 00:07:32 +08:00
huangyuhui 44d1f9cf7d LiteLoader installing 2018-02-05 23:53:57 +08:00
mslxl 826a6bf868 Fix Unable move temp file on Arch Linux 2018-02-05 16:24:44 +08:00
huangyuhui 3abb96e5b9 Temporarily fix spacing missing. Closes #264 2018-02-05 00:23:58 +08:00
huangyuhui 228fddf902 Fix always throwing IOException when downloading game asset index 2018-02-04 21:26:55 +08:00
huangyuhui 1516802a8d Fix the layout of task executor pane 2018-02-04 21:26:25 +08:00
huangyuhui c7589e4e3c Fix official optifine download 2018-02-04 12:33:09 +08:00
huangyuhui a502785209 character selection 2018-02-03 16:48:30 +08:00
huangyuhui c923ed69a0 Update & Crash 2018-02-02 17:45:29 +08:00
huangyuhui e4bd1c758f Delete version folder when installing canceled 2018-02-02 15:38:55 +08:00
huangyuhui ad175fd2f2 Fixed duplicated but different version libraries casuing crash when Forge and OptiFine installed. Closes #261 2018-02-02 15:31:42 +08:00
huangyuhui 190cc18b11 Fixed no forge when install a version with its name not the default one. Closes #208 2018-02-02 14:40:59 +08:00
huangyuhui 55e7b8901a Forbids interrupted exception shown 2018-02-02 14:25:21 +08:00
huangyuhui 2efb5d7f8f MainPage popup menu: open game directory 2018-02-02 13:51:29 +08:00
huangyuhui f144dd888d Launching messages 2018-02-02 13:44:53 +08:00
huangyuhui 0a4944f120 decompress natives to system temp directory 2018-02-02 11:13:10 +08:00
huangyuhui 7675347889 decompress natives when making launch script 2018-02-02 10:53:59 +08:00
huangyuhui 7ea2b57ece Unable to launch dialog 2018-02-02 10:46:27 +08:00
huangyuhui cec8249b24 Removed -XstartOnFirstThread to fix freezed launched game. GH-257 2018-02-01 15:57:55 +08:00
huangyuhui 31c05c9b20 Fixed incomplete GUI. Closes #253 2018-01-29 20:44:58 +08:00
huangyuhui f7aeef3ffc Fix malformd version settings leading to unmodifiable configuration 2018-01-28 15:03:18 +08:00
huangyuhui ccb1ff7f0e Warns user before launching 2018-01-28 14:59:53 +08:00
huangyuhui b707388134 Fix NullPointerException when upgrade from 2.x 2018-01-28 14:16:05 +08:00
huangyuhui 538cc2b75e Attempt to fix #253 2018-01-27 21:27:15 +08:00
huangyuhui b783f3bae9 Install initial modpack 2018-01-27 21:23:24 +08:00
huangyuhui b776015b36 sort i18n 2018-01-27 18:57:54 +08:00
huangyuhui be4ea59984 about 2018-01-27 17:03:06 +08:00
huangyuhui 2f92b65400 installTooltip for java9 2018-01-27 16:25:41 +08:00
huangyuhui 2c7510fe5f Crash and upgrade 2018-01-27 16:16:38 +08:00
huangyuhui 1ff36552e0 Yggdrasil account requries email to be username 2018-01-27 14:14:42 +08:00
huangyuhui 418be689f2 Cached Minecraft jars 2018-01-27 14:05:39 +08:00
huangyuhui 1a572be52e Modpack updates UI 2018-01-27 12:56:45 +08:00
huangyuhui e8316de160 Modpack updates 2018-01-27 01:19:13 +08:00
huangyuhui 12fa94627d TransitionHandler update 2018-01-25 22:41:15 +08:00
huangyuhui 056c0901f2 make launch script 2018-01-24 19:42:04 +08:00
huangyuhui 6db41431ed localization 2018-01-20 11:18:59 +08:00
huangyuhui b90d6b7b8c Update Readme.md for HMCLCore introduction 2018-01-16 19:34:53 +08:00
huangyuhui 4b39c046a7 Prepare for MCBBS modpack 2018-01-12 21:30:09 +08:00
huangyuhui 8edfe7bc9c MessageBox 2018-01-09 21:37:04 +08:00
huangyuhui 9e21e02791 G1GC more options 2018-01-09 17:17:41 +08:00
huangyuhui 8e6b56b2d6 upgrader 2018-01-09 17:14:50 +08:00
huangyuhui 4b65d4da06 Make background transparent? 2018-01-09 00:03:34 +08:00
huangyuhui a40c5fdd40 Turn to java 2018-01-01 23:12:59 +08:00
huangyuhui d8be9abcc5 nothing 2018-01-01 16:21:54 +08:00
huangyuhui d2529cfc23 Merge 'arguments' 2017-11-16 00:13:49 +08:00
huangyuhui b81b38f7e4 remove -Xincgc for Java 9 2017-11-04 11:07:36 +08:00
huangyuhui d9225b9529 Launching 1.13 2017-11-03 13:38:38 +08:00
huangyuhui 4bde509708 Update Gradle 2017-10-23 13:09:50 +08:00
huanghongxun 22877e96c7 Update build.gradle 2017-09-17 16:55:05 +08:00
huanghongxun ae473a0d1a Merge pull request #207 from hellckt/javafx
Fixed invalid forge download url
2017-09-17 16:46:24 +08:00
keteZeng 807f42ad65 Fixed invalid forge download url 2017-09-17 12:08:02 +08:00
曾科特 026986c956 Merge pull request #1 from huanghongxun/javafx
Fixed finding asset objects. Closes #205
2017-09-17 10:40:53 +08:00
huanghongxun 621562a17e Fixed finding asset objects. Closes #205 2017-09-16 20:42:08 +08:00
huanghongxun 5f7f885d99 Added custom game directory. Closes #199 2017-09-04 15:55:18 +08:00
huangyuhui 41337f66a8 Upgrader 2017-08-27 14:23:27 +08:00
huangyuhui 8c61bc1a5e Prepare for proguard 2017-08-27 12:49:56 +08:00
huangyuhui b07144278b Task chain exception catching 2017-08-26 19:09:21 +08:00
huangyuhui c775e8368e Try to asynchronize some time-costly tasks 2017-08-26 10:53:05 +08:00
huangyuhui 008f07cacc Removed wrong imports 2017-08-25 20:22:27 +08:00
huangyuhui ff1a566bf4 Profiles editing and selecting 2017-08-25 20:18:27 +08:00
huangyuhui 3b4359f7eb Fixed logging spaces and tabs missing, unclickable version item, removing 'logging' element when installing forge and liteloader 2017-08-25 13:22:10 +08:00
huangyuhui e70226afba Apache common-compress 2017-08-25 10:12:56 +08:00
huangyuhui cc2c186e7f Load mod list asynchronously 2017-08-25 09:28:55 +08:00
huangyuhui 833247d133 Icon selection 2017-08-25 08:54:33 +08:00
huangyuhui f532df20e6 MultiMC modpack support 2017-08-24 23:53:27 +08:00
huangyuhui 0fa601ad5e Auto-installing 2017-08-24 19:20:15 +08:00
huangyuhui 6da1dc5372 HMCL modpack export 2017-08-24 13:57:01 +08:00
huangyuhui 2e3d9c22e3 Tooltip reflection for advanced options 2017-08-23 12:07:08 +08:00
huangyuhui 86cef86fc9 HMCL modpack output model 2017-08-22 21:07:56 +08:00
huangyuhui d83291eac0 HMCL modpack import 2017-08-22 20:15:26 +08:00
huangyuhui 0cc7d621bc Javadoc 2017-08-22 14:31:35 +08:00
huangyuhui b40b0ec47c Version setting pane context menu 2017-08-21 22:57:45 +08:00
huangyuhui a67bfabea4 Log window category and show lines 2017-08-21 20:59:32 +08:00
huangyuhui be202a6e1a Log Window refined 2017-08-21 13:40:16 +08:00
huangyuhui f92275fa50 orignal log window 2017-08-21 00:37:53 +08:00
huangyuhui 563ea993c6 Minecraft Logging 2017-08-20 19:47:28 +08:00
huangyuhui cbf2a4e7a8 Allow draging files to mod pane 2017-08-20 09:25:37 +08:00
huangyuhui bc53e944af Add mod button in Mod page 2017-08-20 00:20:41 +08:00
huangyuhui 432fd287c9 Fix yggdrasil login. 2017-08-19 23:44:12 +08:00
huangyuhui 383453f9af Launching progress 2017-08-19 21:51:46 +08:00
huangyuhui 27939c6b61 Skin portrait 2017-08-18 22:42:11 +08:00
huangyuhui dc468f1a76 Java Selection 2017-08-18 13:58:49 +08:00
huangyuhui 99f60ea6e5 CurseForge modpack support. Random UserAgent for OptiFine. Fixed mis-repositioning of maronry pane 2017-08-17 13:14:34 +08:00
huangyuhui 1394034160 Settings page refined 2017-08-16 12:02:04 +08:00
huangyuhui 45ac758b52 Mods page refined 2017-08-16 11:06:11 +08:00
huangyuhui bf13c5ff40 ComponentList for settings view 2017-08-16 10:29:59 +08:00
huangyuhui 6179bcdf5b UI improvement 2017-08-14 18:09:02 +08:00
huangyuhui 6121a0cade Version item width limited 2017-08-14 13:11:26 +08:00
huangyuhui a9b044b4d2 localization 2017-08-11 18:16:23 +08:00
huangyuhui 54c4ca4180 Hamburger and drawer 2017-08-10 20:48:18 +08:00
huangyuhui 301130bc0c Clear warnings 2017-08-10 19:56:27 +08:00
huangyuhui 36b23d072c Add files 2017-08-10 19:34:19 +08:00
huangyuhui 66ad048053 Remove tests 2017-08-10 19:08:05 +08:00
huangyuhui 9bd7071297 emmmmm 2017-08-10 18:44:21 +08:00
huangyuhui da66102bc0 Able to launch game now 2017-08-08 23:57:17 +08:00
huangyuhui f1ffa5ca03 Scheduler JavaFX and Swing now have Future 2017-08-06 11:45:12 +08:00
huangyuhui 736a8c1b30 FXML for decorator 2017-08-03 23:54:15 +08:00
huangyuhui 41498b5d93 UI works 2017-08-02 00:33:24 +08:00
huangyuhui 3c32ef39c7 Fixed compile error 2017-08-01 18:56:13 +08:00
huangyuhui 726f92a9c9 Fixed untracked files 2017-08-01 18:12:39 +08:00
huangyuhui f6adbe435e .gitignore 2017-08-01 18:11:47 +08:00
huangyuhui 1410b3b5b1 LICENSE 2017-08-01 18:10:36 +08:00
huangyuhui 6dc2b36d14 HMCLKotlin project 2017-08-01 13:51:48 +08:00
894 changed files with 35341 additions and 54982 deletions

No files matched your search

+13 -5
View File
@@ -4,14 +4,22 @@
hs_err_pid*
.gradle
.nb-gradle
.idea
*.lck
*.1
*.2
*.log
.mine*
NVIDIA
*.json
*.hmd
# gradle build
/build/
/HMCL/build/
/HMCLaF/build/
/HMCLAPI/build/
/HMCLCore/build/
/HMCLCore/build/
# idea output
/out/
/HMCL/out/
/HMCLCore/out/
-36
View File
@@ -1,36 +0,0 @@
<?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>
View File
File renamed without changes.
Executable → Regular
View File
File mode changed.
Executable → Regular
+59 -94
View File
@@ -1,34 +1,7 @@
/*
* 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.security.MessageDigest
import java.util.jar.JarFile
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
plugins {
id "edu.sc.seis.macAppBundle" version "2.1.6"
id "me.tatarka.retrolambda" version "3.5.0"
id 'edu.sc.seis.launch4j' version '2.3.0'
//id "org.jetbrains.kotlin.jvm" version "1.1.2-2"
}
if (!hasProperty('mainClass')) {
ext.mainClass = 'org.jackhuang.hmcl.Main'
@@ -36,30 +9,32 @@ if (!hasProperty('mainClass')) {
def buildnumber = System.getenv("TRAVIS_BUILD_NUMBER")
if (buildnumber == null)
buildnumber = System.getenv("BUILD_NUMBER")
buildnumber = System.getenv("BUILD_NUMBER")
if (buildnumber == null)
buildnumber = "33"
buildnumber = "SNAPSHOT"
def versionroot = System.getenv("VERSION_ROOT")
if (versionroot == null)
versionroot = "2.7.8"
versionroot = "3.0"
String mavenGroupId = 'HMCL'
String mavenVersion = versionroot + '.' + buildnumber
String bundleName = "Hello Minecraft! Launcher"
group = mavenGroupId
version = mavenVersion
String mavenArtifactId = name
dependencies {
compile project(":HMCLCore")
compile rootProject.files("lib/JFoenix.jar")
}
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
@@ -68,41 +43,24 @@ configurations {
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")
}
retrolambda {
javaVersion = JavaVersion.VERSION_1_7
}
jar {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
manifest {
attributes 'Created-By' : 'Copyright(c) 2013-2017 huangyuhui.',
'Main-Class' : mainClass
'Main-Class' : mainClass,
'Multi-Release': "true"
}
doLast {
new File("build/signed").mkdirs()
ant.signjar(signedjar: archivePath, jar: archivePath,
keystore: "HMCL.keystore", storepass: "123456",
alias: "HMCL")
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);
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")
@@ -111,21 +69,6 @@ jar {
}
}
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 {
from(sourceSets.main.resources.srcDirs) {
exclude 'icon.icns'
@@ -140,20 +83,20 @@ task makeExecutable(dependsOn: jar) doLast {
makeExecutableoutjar = jar.archivePath
jar.classifier = ''
}
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".exe")
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);
fos.write(bytes, 0, read)
is.close()
is = new FileInputStream(makeExecutableinjar)
is = new FileInputStream(makeExecutableoutjar)
while((read = is.read(bytes)) != -1)
fos.write(bytes, 0, read);
fos.write(bytes, 0, read)
is.close()
fos.close()
}
task makePackGZ(dependsOn: jar) doLast {
@@ -168,10 +111,10 @@ task makePackGZ(dependsOn: jar) doLast {
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);
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")
@@ -179,19 +122,41 @@ task makePackGZ(dependsOn: jar) doLast {
fileEx.append sha1Hex
}
task macAppCompressed(type: Zip, dependsOn: createApp) doLast {
archiveName "HMCL-$mavenVersion-MacOSApp.zip"
include '**'
destinationDir file("$buildDir/libs/")
from "$buildDir/macApp"
task proguard(type: proguard.gradle.ProGuardTask, dependsOn: jar) {
ext {
def re = jar.classifier
injar = jar.archivePath
jar.classifier = 'optimized'
outjar = jar.archivePath
jar.classifier = re
}
injars(/*["filter": "!META-INF/**,**"], */injar)
String javaHome = System.getProperty("java.home")
if (!javaHome.endsWith("jre"))
javaHome += File.separator + "jre"
libraryjars javaHome + "/lib/rt.jar"
libraryjars javaHome + "/lib/jce.jar"
libraryjars javaHome + "/lib/jsse.jar"
libraryjars javaHome + "/lib/ext/jfxrt.jar"
outjars outjar
configuration 'proguard.pro'
}
macAppBundle {
mainClassName = mainClass
icon = "src/main/icon.icns"
javaProperties.put("apple.laf.useScreenMenuBar", "true")
task combineMETA_INF(type: Jar) {
ext {
def re = jar.classifier
injar = jar.archivePath
jar.classifier = 'optimized'
outjar = jar.archivePath
jar.classifier = re
}
from zipTree(outjar)
from zipTree(injar).matching {
include "META-INF/"
}
}
build.dependsOn makeExecutable
build.dependsOn makePackGZ
build.dependsOn macAppCompressed
build.dependsOn makePackGZ
Executable → Regular
View File
File mode changed.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

+15
View File
@@ -0,0 +1,15 @@
-target 1.8
-dontoptimize
-dontobfuscate
# JFoenix
-keep class com.jfoenix.** {
<fields>;
<methods>;
}
# HMCL
-keep class org.jackhuang.** {
<fields>;
<methods>;
}
+2
View File
@@ -0,0 +1,2 @@
rootProject.name = 'HMCL'
Executable → Regular
View File
File mode changed.
+115 -224
View File
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,235 +17,126 @@
*/
package org.jackhuang.hmcl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
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 com.jfoenix.concurrency.JFXUtilities;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.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.MathUtils;
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 org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.upgrade.AppDataUpgrader;
import org.jackhuang.hmcl.upgrade.IUpgrader;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.util.*;
/**
*
* @author huangyuhui
*/
public final class Main {
import java.io.File;
import java.util.Arrays;
import java.util.ResourceBundle;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
private static final X509TrustManager XTM = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
public final class Main extends Application {
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
@Override
public void start(Stage primaryStage) {
Thread.currentThread().setUncaughtExceptionHandler(CRASH_REPORTER);
@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;
}
public static String shortTitle() {
return "HMCL" + ' ' + LAUNCHER_VERSION;
}
public static final Logger LOGGER = Logger.getLogger(Main.class.getName());
public static void main(String[] args) throws IOException {
{
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.check(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, 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 (StrUtils.isNotBlank(Settings.getInstance().getProxyHost()) && StrUtils.isNotBlank(Settings.getInstance().getProxyPort()) && MathUtils.canParseInt(Settings.getInstance().getProxyPort())) {
HMCLog.log("Initializing customized proxy");
System.setProperty("http.proxyHost", Settings.getInstance().getProxyHost());
System.setProperty("http.proxyPort", Settings.getInstance().getProxyPort());
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());
}
});
}
try {
unpackDefaultLog4jConfiguration();
} catch(IOException e) {
HMCLog.err("Failed to unpack log4j.xml, log window will not work well.", e);
}
MainFrame.showMainFrame();
}
}
public static void invokeUpdate() {
MainFrame.INSTANCE.invokeUpdate();
}
public static final File LOG4J_FILE = new File(MCUtils.getWorkingDirectory("hmcl"), "log4j.xml");
public static void unpackDefaultLog4jConfiguration() throws IOException {
LOG4J_FILE.getParentFile().mkdirs();
if (LOG4J_FILE.exists()) return;
LOG4J_FILE.createNewFile();
try (InputStream is = Main.class.getResourceAsStream("/org/jackhuang/hmcl/log4j.xml");
FileOutputStream fos = new FileOutputStream(LOG4J_FILE)) {
int b;
while ((b = is.read()) != -1)
fos.write(b);
}
}
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;
// 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());
primaryStage.show();
} catch (Throwable e) {
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
}
}
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
try {
// NetworkUtils.setUserAgentSupplier(() -> "Hello Minecraft! Launcher");
Constants.UI_THREAD_SCHEDULER = Constants.JAVAFX_UI_THREAD_SCHEDULER;
UPGRADER.parseArguments(VersionNumber.asVersion(VERSION), Arrays.asList(args));
Logging.LOG.info("*** " + TITLE + " ***");
UPDATE_CHECKER.process(false)
.then(Task.of(Schedulers.javafx(), () -> {
if (UPDATE_CHECKER.isOutOfDate())
Controllers.showUpdate();
}))
.start();
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() {
JFXUtilities.runInFX(() -> {
stopWithoutPlatform();
Platform.exit();
});
}
public static void stopWithoutPlatform() {
JFXUtilities.runInFX(() -> {
Controllers.getStage().close();
Logging.LOG.info("Shutting down executor services.");
Schedulers.shutdown();
Controllers.shutdown();
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
});
}
public static String i18n(String key) {
try {
return RESOURCE_BUNDLE.getString(key);
} catch (Exception e) {
Logging.LOG.log(Level.SEVERE, "Cannot find key " + key + " in resource bundle", e);
return key;
}
}
public static String i18n(String key, Object... formatArgs) {
return String.format(i18n(key), formatArgs);
}
public static File getWorkingDirectory(String folder) {
String home = System.getProperty("user.home", ".");
switch (OperatingSystem.CURRENT_OS) {
case LINUX:
return new File(home, "." + folder + "/");
case WINDOWS:
String appdata = System.getenv("APPDATA");
return new File(Lang.nonNull(appdata, home), "." + folder + "/");
case OSX:
return new File(home, "Library/Application Support/" + folder);
default:
return new File(home, folder + "/");
}
}
public static final File MINECRAFT_DIRECTORY = getWorkingDirectory("minecraft");
public static final File HMCL_DIRECTORY = getWorkingDirectory("hmcl");
public static final String VERSION = "@HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING@";
public static final String NAME = "HMCL";
public static final String TITLE = NAME + " " + VERSION;
public static final ResourceBundle RESOURCE_BUNDLE = Settings.INSTANCE.getLocale().getResourceBundle();
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(VersionNumber.asVersion(VERSION));
public static final IUpgrader UPGRADER = new AppDataUpgrader();
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
public static final String CONTACT = "http://huangyuhui.duapp.com/hmcl.php";
public static final String PUBLISH = "http://www.mcbbs.net/thread-142335-1-1.html";
}
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
*
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -15,22 +15,31 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.core.install;
package org.jackhuang.hmcl.event;
import java.io.Serializable;
import java.util.Comparator;
import org.jackhuang.hmcl.core.install.InstallerVersionList.InstallerVersion;
import org.jackhuang.hmcl.util.VersionNumber;
/**
*
* Result: Deny if do not upgrade HMCL.
*
* @author huangyuhui
*/
public class InstallerVersionNewerComparator implements Comparator<InstallerVersion>, Serializable {
public final class OutOfDateEvent extends Event {
private final VersionNumber version;
private static final long serialVersionUID = 14758562453742645L;
public OutOfDateEvent(Object source, VersionNumber version) {
super(source);
this.version = version;
}
public VersionNumber getVersion() {
return version;
}
@Override
public int compare(InstallerVersion o1, InstallerVersion o2) {
return o2.compareTo(o1);
public boolean hasResult() {
return true;
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,23 +15,33 @@
* 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;
package org.jackhuang.hmcl.event;
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.
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
* @author huangyuhui
*/
public class ProfileChangedEvent extends SimpleEvent<Profile> {
public ProfileChangedEvent(Object source, Profile value) {
super(source, value);
public final class ProfileChangedEvent extends Event {
private final Profile profile;
/**
* Constructor.
*
* @param source {@link org.jackhuang.hmcl.setting.Settings}
* @param profile the new profile.
*/
public ProfileChangedEvent(Object source, Profile profile) {
super(source);
this.profile = profile;
}
public Profile getProfile() {
return profile;
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,21 +15,25 @@
* 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;
package org.jackhuang.hmcl.event;
/**
* 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}
* This event is fired on the {@link org.jackhuang.hmcl.event.EventBus#EVENT_BUS}
*
* @author huangyuhui
*/
public class ProfileLoadingEvent extends EventObject {
public class ProfileLoadingEvent extends Event {
/**
* Constructor.
*
* @param source {@link org.jackhuang.hmcl.setting.Settings}
*/
public ProfileLoadingEvent(Object source) {
super(source);
}
}
@@ -0,0 +1,200 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Scheduler;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.util.NetworkUtils;
import java.io.File;
import java.net.Proxy;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
public final class AccountHelper {
public static final AccountHelper INSTANCE = new AccountHelper();
private AccountHelper() {}
public static final File SKIN_DIR = new File(Main.HMCL_DIRECTORY, "skins");
public static void loadSkins() {
loadSkins(Proxy.NO_PROXY);
}
public static void loadSkins(Proxy proxy) {
for (Account account : Settings.INSTANCE.getAccounts()) {
if (account instanceof YggdrasilAccount) {
new SkinLoadTask((YggdrasilAccount) account, proxy, false).start();
}
}
}
public static Task loadSkinAsync(YggdrasilAccount account) {
return loadSkinAsync(account, Settings.INSTANCE.getProxy());
}
public static Task loadSkinAsync(YggdrasilAccount account, Proxy proxy) {
return new SkinLoadTask(account, proxy, false);
}
public static Task refreshSkinAsync(YggdrasilAccount account) {
return refreshSkinAsync(account, Settings.INSTANCE.getProxy());
}
public static Task refreshSkinAsync(YggdrasilAccount account, Proxy proxy) {
return new SkinLoadTask(account, proxy, true);
}
private static File getSkinFile(String name) {
return new File(SKIN_DIR, name + ".png");
}
public static Image getSkin(YggdrasilAccount account) {
return getSkin(account, 1);
}
public static Image getSkin(YggdrasilAccount account, double scaleRatio) {
if (account.getCharacter() == null)
return getDefaultSkin(account, scaleRatio);
File file = getSkinFile(account.getCharacter());
if (file.exists()) {
Image original = new Image("file:" + file.getAbsolutePath());
return new Image("file:" + file.getAbsolutePath(),
original.getWidth() * scaleRatio,
original.getHeight() * scaleRatio,
false, false);
}
return getDefaultSkin(account, scaleRatio);
}
public static Image getSkinImmediately(YggdrasilAccount account, GameProfile profile, double scaleRatio, Proxy proxy) throws Exception {
String name = profile.getName();
File file = getSkinFile(name);
downloadSkin(account, profile, true, proxy);
if (!file.exists())
return getDefaultSkin(account, scaleRatio);
String url = "file:" + file.getAbsolutePath();
return scale(url, scaleRatio);
}
public static Rectangle2D getViewport(double scaleRatio) {
double size = 8.0 * scaleRatio;
return new Rectangle2D(size, size, size, size);
}
private static class SkinLoadTask extends Task {
private final YggdrasilAccount account;
private final Proxy proxy;
private final boolean refresh;
private final List<Task> dependencies = new LinkedList<>();
public SkinLoadTask(YggdrasilAccount account, Proxy proxy) {
this(account, proxy, false);
}
public SkinLoadTask(YggdrasilAccount account, Proxy proxy, boolean refresh) {
this.account = account;
this.proxy = proxy;
this.refresh = refresh;
}
@Override
public Scheduler getScheduler() {
return Schedulers.io();
}
@Override
public Collection<Task> getDependencies() {
return dependencies;
}
@Override
public void execute() throws Exception {
if (!account.isLoggedIn() && (account.getCharacter() == null || refresh))
DialogController.logIn(account);
downloadSkin(account, refresh, proxy);
}
}
private static void downloadSkin(YggdrasilAccount account, GameProfile profile, boolean refresh, Proxy proxy) throws Exception {
account.clearCache();
Optional<Texture> texture = account.getSkin(profile);
if (!texture.isPresent()) return;
String url = texture.get().getUrl();
File file = getSkinFile(profile.getName());
if (!refresh && file.exists())
return;
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
}
private static void downloadSkin(YggdrasilAccount account, boolean refresh, Proxy proxy) throws Exception {
account.clearCache();
if (account.getCharacter() == null) return;
Optional<Texture> texture = account.getSkin();
if (!texture.isPresent()) return;
String url = texture.get().getUrl();
File file = getSkinFile(account.getCharacter());
if (!refresh && file.exists())
return;
new FileDownloadTask(NetworkUtils.toURL(url), file, proxy).run();
}
public static Image scale(String url, double scaleRatio) {
Image origin = new Image(url);
return new Image(url,
origin.getWidth() * scaleRatio,
origin.getHeight() * scaleRatio,
false, false);
}
public static Image getSteveSkin(double scaleRatio) {
return scale("/assets/img/steve.png", 4);
}
public static Image getAlexSkin(double scaleRatio) {
return scale("/assets/img/alex.png", 4);
}
public static Image getDefaultSkin(Account account, double scaleRatio) {
if (account == null)
return getSteveSkin(scaleRatio);
int type = account.getUUID().hashCode() & 1;
if (type == 1)
return getAlexSkin(scaleRatio);
else
return getSteveSkin(scaleRatio);
}
}
@@ -0,0 +1,47 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.GameBuilder;
import org.jackhuang.hmcl.setting.Profile;
import java.net.Proxy;
/**
* @author huangyuhui
*/
public class HMCLDependencyManager extends DefaultDependencyManager {
private final Profile profile;
public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider) {
this(profile, downloadProvider, Proxy.NO_PROXY);
}
public HMCLDependencyManager(Profile profile, DownloadProvider downloadProvider, Proxy proxy) {
super(profile.getRepository(), downloadProvider, proxy);
this.profile = profile;
}
@Override
public GameBuilder gameBuilder() {
return new HMCLGameBuilder(profile);
}
}
@@ -0,0 +1,40 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.download.DefaultGameBuilder;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Task;
/**
* @author huangyuhui
*/
public class HMCLGameBuilder extends DefaultGameBuilder {
private final Profile profile;
public HMCLGameBuilder(Profile profile) {
super(profile.getDependency());
this.profile = profile;
}
@Override
protected Task downloadGameAsync(String gameVersion, Version version) {
return new HMCLGameDownloadTask(profile, gameVersion, version);
}
}
@@ -0,0 +1,77 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.NetworkUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
/**
* @author huangyuhui
*/
public class HMCLGameDownloadTask extends Task {
private final Profile profile;
private final String gameVersion;
private final Version version;
private final List<Task> dependencies = new LinkedList<>();
public HMCLGameDownloadTask(Profile profile, String gameVersion, Version version) {
this.profile = profile;
this.gameVersion = gameVersion;
this.version = version;
setSignificance(TaskSignificance.MINOR);
}
@Override
public List<Task> getDependencies() {
return dependencies;
}
@Override
public void execute() {
File jar = profile.getRepository().getVersionJar(version);
File cache = new File(Settings.INSTANCE.getCommonPath(), "jars/" + gameVersion + ".jar");
if (cache.exists())
try {
FileUtils.copyFile(cache, jar);
return;
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Unable to copy cached Minecraft jar from " + cache + " to " + jar, e);
}
dependencies.add(new FileDownloadTask(
NetworkUtils.toURL(profile.getDependency().getDownloadProvider().injectURL(version.getDownloadInfo().getUrl())),
cache,
profile.getDependency().getProxy(),
version.getDownloadInfo().getSha1()
).then(Task.of(v -> FileUtils.copyFile(cache, jar))));
}
}
@@ -0,0 +1,51 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.launch.DefaultLauncher;
import org.jackhuang.hmcl.launch.ProcessListener;
import java.util.List;
/**
* @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 void appendJvmArgs(List<String> result) {
super.appendJvmArgs(result);
result.add("-Dminecraft.launcher.version=" + Main.VERSION);
result.add("-Dminecraft.launcher.brand=" + Main.NAME);
}
}
@@ -0,0 +1,206 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.event.RefreshingVersionsEvent;
import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Logging;
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;
}
private boolean useSelf(String version, String assetId) {
VersionSetting vs = profile.getVersionSetting(version);
return new File(getBaseDirectory(), "assets/indexes/" + assetId + ".json").exists() || vs.isNoCommon();
}
@Override
public File getAssetDirectory(String version, String assetId) {
if (useSelf(version, assetId))
return super.getAssetDirectory(version, assetId);
else
return new File(Settings.INSTANCE.getCommonPath(), "assets");
}
@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
public File getLibraryFile(Version version, Library lib) {
VersionSetting vs = profile.getVersionSetting(version.getId());
File self = super.getLibraryFile(version, lib);
if (self.exists() || vs.isNoCommon())
return self;
else
return new File(Settings.INSTANCE.getCommonPath(), "libraries/" + lib.getPath());
}
@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);
}
}
@Override
public void refreshVersions() {
EventBus.EVENT_BUS.fireEvent(new RefreshingVersionsEvent(this));
refreshVersionsImpl();
EventBus.EVENT_BUS.fireEvent(new RefreshedVersionsEvent(this));
}
public void changeDirectory(File newDirectory) {
setBaseDirectory(newDirectory);
refreshVersionsAsync().start();
}
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 getVersionIcon(String id) {
return new File(getVersionRoot(id), "icon.png");
}
public void saveVersionSetting(String id) {
if (!versionSettings.containsKey(id))
return;
Lang.invoke(() -> FileUtils.writeText(getVersionSettingFile(id), GSON.toJson(versionSettings.get(id))));
}
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()
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
.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\"}";
}
@@ -0,0 +1,97 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.task.TaskResult;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.ZipEngine;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Export the game to a mod pack file.
*/
public class HMCLModpackExportTask extends TaskResult<ZipEngine> {
private final DefaultGameRepository repository;
private final String version;
private final List<String> whitelist;
private final Modpack modpack;
private final File output;
private final String id;
public HMCLModpackExportTask(DefaultGameRepository repository, String version, List<String> whitelist, Modpack modpack, File output) {
this(repository, version, whitelist, modpack, output, ID);
}
/**
* @param output mod pack file.
* @param version to locate version.json
*/
public HMCLModpackExportTask(DefaultGameRepository repository, String version, List<String> whitelist, Modpack modpack, File output, String id) {
this.repository = repository;
this.version = version;
this.whitelist = whitelist;
this.modpack = modpack;
this.output = output;
this.id = id;
onDone().register(event -> {
if (event.isFailed()) output.delete();
});
}
@Override
public String getId() {
return id;
}
@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 (ZipEngine zip = new ZipEngine(output)) {
zip.putDirectory(repository.getRunDirectory(version), (String pathName, Boolean isDirectory) -> {
for (String s : blackList)
if (isDirectory) {
if (pathName.startsWith(s + "/"))
return null;
} else if (pathName.equals(s))
return null;
for (String s : whitelist)
if (pathName.equals(s + (isDirectory ? "/" : "")))
return "minecraft/" + pathName;
return null;
});
Version mv = repository.getVersion(version).resolve(repository);
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
zip.putTextFile(Constants.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
zip.putTextFile(Constants.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
}
}
public static final String ID = "zip_engine";
}
@@ -0,0 +1,100 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.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.CompressingUtils;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
public final class HMCLModpackInstallTask extends Task {
private final File zipFile;
private final String name;
private final HMCLGameRepository repository;
private final Modpack modpack;
private final File run;
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;
this.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 = Constants.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, "minecraft/", it -> !Objects.equals(it, "minecraft/pack.json"), 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 = Constants.GSON.fromJson(json, Version.class).setJar(null);
dependencies.add(new VersionJsonSaveTask(repository, version));
dependencies.add(new MinecraftInstanceTask<>(zipFile, "minecraft/", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
}
public static final String MODPACK_TYPE = "HMCL";
}
@@ -0,0 +1,96 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.util.CompressingUtils;
import org.jackhuang.hmcl.util.JsonUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
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", // HMCL
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
"minetweaker.log", // Mods
"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.
* @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(File file) throws IOException, JsonParseException {
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json");
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, Modpack.class);
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json");
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());
}
}
@@ -0,0 +1,24 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
public final class HMCLModpackManifest {
public static final HMCLModpackManifest INSTANCE = new HMCLModpackManifest();
private HMCLModpackManifest() {}
}
@@ -0,0 +1,408 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import com.jfoenix.concurrency.JFXUtilities;
import javafx.application.Platform;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.ServerDisconnectException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.launch.*;
import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
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.MessageBox;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public final class LauncherHelper {
public static final LauncherHelper INSTANCE = new LauncherHelper();
private LauncherHelper(){}
private TaskExecutor executor;
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(() -> {});
public void launch(String selectedVersion, File scriptFile) {
Profile profile = Settings.INSTANCE.getSelectedProfile();
GameRepository repository = profile.getRepository();
Account account = Settings.INSTANCE.getSelectedAccount();
if (account == null)
throw new IllegalStateException("No account");
Version version = repository.getVersion(selectedVersion);
VersionSetting setting = profile.getVersionSetting(selectedVersion);
Platform.runLater(() -> {
try {
checkGameState(profile, setting, version, () -> Schedulers.newThread().schedule(() -> launch0(selectedVersion, scriptFile)));
} catch (InterruptedException ignore) {
}
});
}
private void launch0(String selectedVersion, File scriptFile) {
Profile profile = Settings.INSTANCE.getSelectedProfile();
GameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = repository.getVersion(selectedVersion);
Account account = Settings.INSTANCE.getSelectedAccount();
VersionSetting setting = profile.getVersionSetting(selectedVersion);
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
executor = Task.of(Schedulers.javafx(), () -> Controllers.dialog(launchingStepsPane))
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES)))
.then(variables -> {
if (setting.isNotCheckGame())
return null;
else
return dependencyManager.checkGameCompletionAsync(version);
})
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS)))
.then(new CurseCompletionTask(dependencyManager, selectedVersion))
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN)))
.then(Task.of(Main.i18n("account.methods"), variables -> {
try {
try {
variables.set("account", account.logIn());
} catch (ServerDisconnectException e) {
if (account.canPlayOffline())
variables.set("account", account.playOffline());
else
throw e;
}
} catch (AuthenticationException e) {
variables.set("account", DialogController.logIn(account));
JFXUtilities.runInFX(() -> Controllers.dialog(launchingStepsPane));
}
}))
.then(Task.of(Schedulers.javafx(), () -> emitStatus(LoadingState.LAUNCHING)))
.then(Task.of(variables -> {
variables.set("launcher", new HMCLGameLauncher(
repository, selectedVersion, variables.get("account"), setting.toLaunchOptions(profile.getGameDir()), new HMCLProcessListener(variables.get("account"), setting, gameVersion.isPresent())
));
}))
.then(variables -> {
DefaultLauncher launcher = variables.get("launcher");
if (scriptFile == null) {
return new LaunchTask<>(launcher::launch).setName(Main.i18n("version.launch"));
} else {
return new LaunchTask<>(() -> {
launcher.makeLaunchScript(scriptFile);
return null;
}).setName(Main.i18n("version.launch_script"));
}
})
.then(Task.of(variables -> {
if (scriptFile == null) {
ManagedProcess process = variables.get(LaunchTask.LAUNCH_ID);
PROCESSES.add(process);
if (setting.getLauncherVisibility() == LauncherVisibility.CLOSE)
Main.stopApplication();
else
launchingStepsPane.setCancel(() -> {
process.stop();
Controllers.closeDialog();
});
} else
Platform.runLater(() ->
Controllers.dialog(Main.i18n("version.launch_script.success", scriptFile.getAbsolutePath())));
}))
.executor();
launchingStepsPane.setExecutor(executor);
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.closeDialog();
} else {
Platform.runLater(() -> {
if (executor.getLastException() != null)
Controllers.dialog(I18nException.getStackTrace(executor.getLastException()),
scriptFile == null ? Main.i18n("launch.failed") : Main.i18n("version.launch_script.failed"),
MessageBox.ERROR_MESSAGE, Controllers::closeDialog);
else
Controllers.closeDialog();
});
}
LauncherHelper.this.executor = null;
}
});
executor.start();
}
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
boolean flag = false, suggest = true;
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
JavaVersion java = setting.getJavaVersion();
if (java == null) {
Controllers.dialog(Main.i18n("launch.wrong_javadir"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
setting.setJava(null);
java = JavaVersion.fromCurrentEnvironment();
flag = true;
}
if (java.getParsedVersion() < JavaVersion.JAVA_8) {
Controllers.dialog(Main.i18n("launch.advice.newer_java"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (java.getParsedVersion() >= JavaVersion.JAVA_9 && gameVersion.compareTo(VersionNumber.asVersion("1.12.5")) < 0 && version.getMainClass().contains("launchwrapper")) {
Controllers.dialog(Main.i18n("launch.advice.java9"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, null);
suggest = false;
flag = true;
}
if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
org.jackhuang.hmcl.util.Platform.IS_64_BIT) {
Controllers.dialog(Main.i18n("launch.advice.different_platform"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (java.getPlatform() == org.jackhuang.hmcl.util.Platform.BIT_32 &&
setting.getMaxMemory() > 1.5 * 1024) {
Controllers.dialog(Main.i18n("launch.advice.too_large_memory_for_32bit"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
Controllers.dialog(Main.i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE, onAccept);
flag = true;
}
if (flag) {
if (suggest && Controllers.getDialogContent() instanceof MessageDialogPane)
((MessageDialogPane) Controllers.getDialogContent()).disableClosingDialog();
} else
onAccept.run();
}
public static void stopManagedProcesses() {
while (!PROCESSES.isEmpty())
Optional.ofNullable(PROCESSES.poll()).ifPresent(ManagedProcess::stop);
}
public void emitStatus(LoadingState state) {
launchingStepsPane.setTitle(state.getLocalizedMessage());
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
Controllers.dialog(launchingStepsPane);
}
private void checkExit(LauncherVisibility v) {
switch (v) {
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 Main.stop(), log window will be halt immediately.
Main.stopWithoutPlatform();
});
break;
}
}
private static class LaunchTask<T> extends TaskResult<T> {
private final ExceptionalSupplier<T, Exception> supplier;
public LaunchTask(ExceptionalSupplier<T, Exception> supplier) {
this.supplier = supplier;
}
@Override
public void execute() throws Exception {
try {
setResult(supplier.get());
} catch (PermissionException e) {
throw new I18nException(Main.i18n("launch.failed.executable_permission"), e);
} catch (ProcessCreationException e) {
throw new I18nException(Main.i18n("launch.failed.creating_process") + e.getLocalizedMessage(), e);
} catch (NotDecompressingNativesException e) {
throw new I18nException(Main.i18n("launch.failed.decompressing_natives") + e.getLocalizedMessage(), e);
}
}
@Override
public String getId() {
return LAUNCH_ID;
}
static final String LAUNCH_ID = "launch";
}
/**
* 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 VersionSetting setting;
private final Map<String, String> forbiddenTokens;
private final LauncherVisibility visibility;
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, VersionSetting setting, boolean detectWindow) {
this.setting = setting;
this.detectWindow = detectWindow;
if (authInfo == null)
forbiddenTokens = Collections.emptyMap();
else
forbiddenTokens = Lang.mapOf(
new Pair<>(authInfo.getAccessToken(), "<access token>"),
new Pair<>(authInfo.getUserId(), "<uuid>"),
new Pair<>(authInfo.getUsername(), "<player>")
);
visibility = setting.getLauncherVisibility();
logs = new LinkedList<>();
}
@Override
public void setProcess(ManagedProcess process) {
this.process = process;
if (setting.isShowLogs())
Platform.runLater(() -> {
logWindow = new LogWindow();
logWindow.show();
latch.countDown();
});
}
@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(new Pair<>(log, level));
if (logs.size() > Settings.INSTANCE.getLogLines())
logs.removeFirst();
if (setting.isShowLogs()) {
Lang.invoke(() -> {
latch.await();
logWindow.waitForLoaded();
});
Platform.runLater(() -> logWindow.logLine(log, level));
}
if (!lwjgl && log.contains("LWJGL Version: ") || !detectWindow) {
lwjgl = true;
switch (visibility) {
case HIDE_AND_REOPEN:
Platform.runLater(() -> {
Controllers.getStage().hide();
emitStatus(LoadingState.DONE);
});
break;
case CLOSE:
throw new Error("Never come to here");
case KEEP:
// No operations here
break;
case HIDE:
Platform.runLater(() -> {
Controllers.getStage().close();
emitStatus(LoadingState.DONE);
});
break;
}
}
}
@Override
public void onExit(int exitCode, ExitType exitType) {
if (exitType == ExitType.INTERRUPTED)
return;
if (exitType != ExitType.NORMAL && logWindow == null)
Platform.runLater(() -> {
logWindow = new LogWindow();
switch (exitType) {
case JVM_ERROR:
logWindow.setTitle(Main.i18n("launch.failed.cannot_create_jvm"));
break;
case APPLICATION_ERROR:
logWindow.setTitle(Main.i18n("launch.failed.exited_abnormally"));
break;
}
logWindow.show();
logWindow.onDone.register(() -> {
logWindow.logLine("Commands: " + StringUtils.makeCommand(process.getCommands()), Log4jLevel.INFO);
for (Map.Entry<String, Log4jLevel> entry : logs)
logWindow.logLine(entry.getKey(), entry.getValue());
});
});
checkExit(visibility);
}
}
}
@@ -0,0 +1,38 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.Main;
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 Main.i18n(key);
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,20 +15,17 @@
* 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.core.mod;
package org.jackhuang.hmcl.game;
/**
*
* @author huang
* @author huangyuhui
*/
public interface ModAdviser {
ModSuggestion advise(String fileName, boolean isDirectory);
public static enum ModSuggestion {
enum ModSuggestion {
SUGGESTED,
NORMAL,
HIDDEN
}
}
@@ -0,0 +1,171 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.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.FinalizedCallback;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Constants;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Lang;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Optional;
public final class ModpackHelper {
private ModpackHelper() {}
public static Modpack readModpackManifest(File file) throws UnsupportedModpackException {
try {
return CurseManifest.readCurseForgeModpackManifest(file);
} catch (Exception e) {
// ignore it, not a valid CurseForge modpack.
}
try {
return HMCLModpackManager.readHMCLModpackManifest(file);
} catch (Exception e) {
// ignore it, not a valid HMCL modpack.
}
try {
return MultiMCInstanceConfiguration.readMultiMCModpackManifest(file);
} catch (Exception e) {
// ignore it, not a valid MultiMC modpack.
}
throw new UnsupportedModpackException(file.toString());
}
public static <T> ModpackConfiguration<T> readModpackConfiguration(File file) throws IOException {
if (!file.exists())
throw new FileNotFoundException(file.getPath());
else
try {
return Constants.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<T>>() {
}.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 getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) {
profile.getRepository().markVersionAsModpack(name);
FinalizedCallback finalizeTask = (variables, isDependentsSucceeded) -> {
profile.getRepository().refreshVersions();
VersionSetting vs = profile.specializeVersionSetting(name);
profile.getRepository().undoMark(name);
if (vs != null)
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
};
if (modpack.getManifest() instanceof CurseManifest)
return new CurseInstallTask(profile.getDependency(), zipFile, ((CurseManifest) modpack.getManifest()), name)
.finalized(finalizeTask);
else if (modpack.getManifest() instanceof HMCLModpackManifest)
return new HMCLModpackInstallTask(profile, zipFile, modpack, name)
.finalized(finalizeTask);
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
.finalized(finalizeTask)
.with(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
}
public static Task getUpdateTask(Profile profile, File zipFile, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException {
Modpack modpack = ModpackHelper.readModpackManifest(zipFile);
switch (configuration.getType()) {
case CurseInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof CurseManifest))
throw new MismatchedModpackTypeException(CurseInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new CurseInstallTask(profile.getDependency(), zipFile, (CurseManifest) modpack.getManifest(), name);
case MultiMCModpackInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof MultiMCInstanceConfiguration))
throw new MismatchedModpackTypeException(MultiMCModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, (MultiMCInstanceConfiguration) modpack.getManifest(), name);
case HMCLModpackInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
throw new MismatchedModpackTypeException(HMCLModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return 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());
}
}
}
@@ -0,0 +1,50 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.mod.MultiMCInstanceConfiguration;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.Scheduler;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import java.util.Objects;
public final class MultiMCInstallVersionSettingTask extends Task {
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;
}
@Override
public Scheduler getScheduler() {
return Schedulers.javafx();
}
@Override
public void execute() {
VersionSetting vs = Objects.requireNonNull(profile.specializeVersionSetting(version));
ModpackHelper.toVersionSetting(manifest, vs);
}
}
@@ -0,0 +1,104 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorBuildInfo;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerResponse;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
import org.jackhuang.hmcl.auth.yggdrasil.MojangYggdrasilProvider;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
/**
* @author huangyuhui
*/
public final class Accounts {
private Accounts() {}
public static final String OFFLINE_ACCOUNT_KEY = "offline";
public static final String YGGDRASIL_ACCOUNT_KEY = "yggdrasil";
public static final String AUTHLIB_INJECTOR_ACCOUNT_KEY = "authlibInjector";
public static final Map<String, AccountFactory<?>> ACCOUNT_FACTORY = Lang.mapOf(
new Pair<>(OFFLINE_ACCOUNT_KEY, OfflineAccountFactory.INSTANCE),
new Pair<>(YGGDRASIL_ACCOUNT_KEY, new YggdrasilAccountFactory(MojangYggdrasilProvider.INSTANCE)),
new Pair<>(AUTHLIB_INJECTOR_ACCOUNT_KEY, new AuthlibInjectorAccountFactory(Accounts::downloadAuthlibInjector))
);
private static final Map<String, String> AUTHLIB_INJECTOR_SERVER_NAMES = new HashMap<>();
public static String getAccountType(Account account) {
if (account instanceof OfflineAccount) return OFFLINE_ACCOUNT_KEY;
else if (account instanceof AuthlibInjectorAccount) return AUTHLIB_INJECTOR_ACCOUNT_KEY;
else if (account instanceof YggdrasilAccount) return YGGDRASIL_ACCOUNT_KEY;
else return YGGDRASIL_ACCOUNT_KEY;
}
static String getAccountId(Account account) {
return getAccountId(account.getUsername(), account.getCharacter());
}
static String getAccountId(String username, String character) {
return username + ":" + character;
}
private static String downloadAuthlibInjector() throws Exception {
AuthlibInjectorBuildInfo buildInfo = AuthlibInjectorBuildInfo.requestBuildInfo();
File jar = new File(Main.HMCL_DIRECTORY, "authlib-injector.jar");
File local = new File(Main.HMCL_DIRECTORY, "authlib-injector.txt");
int buildNumber = 0;
try {
buildNumber = Integer.parseInt(FileUtils.readText(local));
} catch (IOException | NumberFormatException ignore) {
}
if (buildNumber < buildInfo.getBuildNumber()) {
new FileDownloadTask(new URL(buildInfo.getUrl()), jar).run();
FileUtils.writeText(local, String.valueOf(buildInfo.getBuildNumber()));
}
return jar.getAbsolutePath();
}
public static String getAuthlibInjectorServerName(String serverIp) {
if (AUTHLIB_INJECTOR_SERVER_NAMES.containsKey(serverIp))
return AUTHLIB_INJECTOR_SERVER_NAMES.get(serverIp);
else {
try {
AuthlibInjectorServerResponse response = Constants.GSON.fromJson(NetworkUtils.doGet(NetworkUtils.toURL(serverIp)), AuthlibInjectorServerResponse.class);
AUTHLIB_INJECTOR_SERVER_NAMES.put(serverIp, response.getMeta().getServerName());
return response.getMeta().getServerName();
} catch (JsonParseException | IOException | NullPointerException e) {
return null;
}
}
}
}
+178 -230
View File
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui
*
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -17,326 +17,274 @@
*/
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.core.download.DownloadType;
import com.google.gson.annotations.SerializedName;
import java.awt.Font;
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;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.util.JavaVersion;
/**
*
* @author huangyuhui
*/
public final class Config implements Cloneable {
import java.util.*;
final class Config {
@SerializedName("last")
private String last;
private String selectedProfile = "";
@SerializedName("backgroundType")
private int backgroundImageType = 0;
@SerializedName("bgpath")
private String bgpath;
private String backgroundImage = null;
@SerializedName("commonpath")
private String commonpath;
@SerializedName("clientToken")
private final String clientToken;
private String commonDirectory = Main.MINECRAFT_DIRECTORY.getAbsolutePath();
@SerializedName("hasProxy")
private boolean hasProxy = false;
@SerializedName("hasProxyAuth")
private boolean hasProxyAuth = false;
@SerializedName("proxyType")
private int proxyType = 0;
@SerializedName("proxyHost")
private String proxyHost;
private String proxyHost = null;
@SerializedName("proxyPort")
private String proxyPort;
private String proxyPort = null;
@SerializedName("proxyUserName")
private String proxyUserName;
private String proxyUser = null;
@SerializedName("proxyPassword")
private String proxyPassword;
@SerializedName("enableShadow")
private boolean enableShadow;
@SerializedName("enableBlur")
private boolean enableBlur;
@SerializedName("enableAnimation")
private boolean enableAnimation;
@SerializedName("decorated")
private boolean decorated;
private String proxyPass = null;
@SerializedName("theme")
private String theme;
private String theme = null;
@SerializedName("java")
private List<JdkVersion> java;
private List<JavaVersion> java = null;
@SerializedName("localization")
private String localization;
@SerializedName("logintype")
private int logintype;
@SerializedName("downloadtype")
private int downloadtype;
private int downloadType = 0;
@SerializedName("configurations")
private TreeMap<String, Profile> configurations;
@SerializedName("auth")
private Map<String, Map> auth;
private Map<String, Profile> configurations = new TreeMap<>();
@SerializedName("accounts")
private List<Map<Object, Object>> accounts = new LinkedList<>();
@SerializedName("selectedAccount")
private String selectedAccount = "";
@SerializedName("fontFamily")
private String fontFamily;
private String fontFamily = "Consolas";
@SerializedName("fontSize")
private int fontSize;
private double fontSize = 12;
@SerializedName("logLines")
private int logLines;
private int logLines = 100;
public Config() {
clientToken = UUID.randomUUID().toString();
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();
@SerializedName("authlibInjectorServerURLs")
private Set<String> authlibInjectorServerURLs = new HashSet<>();
public String getSelectedProfile() {
return selectedProfile;
}
public List<JdkVersion> getJava() {
return java == null ? java = new ArrayList<>() : java;
public void setSelectedProfile(String selectedProfile) {
this.selectedProfile = selectedProfile;
Settings.INSTANCE.save();
}
public Theme getTheme() {
if (!Theme.THEMES.containsKey(theme))
theme = LAFTheme.BLUE.id;
return Theme.THEMES.get(theme);
public String getBackgroundImage() {
return backgroundImage;
}
public void setTheme(String theme) {
this.theme = theme;
HMCLApi.EVENT_BUS.fireChannel(new ThemeChangedEvent(this, getTheme()));
Settings.save();
public void setBackgroundImage(String backgroundImage) {
this.backgroundImage = backgroundImage;
Settings.INSTANCE.save();
}
public boolean isDecorated() {
return decorated;
public int getBackgroundImageType() {
return backgroundImageType;
}
public void setDecorated(boolean decorated) {
this.decorated = decorated;
public void setBackgroundImageType(int backgroundImageType) {
this.backgroundImageType = backgroundImageType;
Settings.INSTANCE.save();
}
public boolean isEnableShadow() {
return enableShadow && OS.os() == OS.WINDOWS;
public String getCommonDirectory() {
return commonDirectory;
}
public void setEnableShadow(boolean enableShadow) {
this.enableShadow = enableShadow;
Settings.save();
public void setCommonDirectory(String commonDirectory) {
this.commonDirectory = commonDirectory;
Settings.INSTANCE.save();
}
public boolean isEnableAnimation() {
return enableAnimation;
public boolean isHasProxy() {
return hasProxy;
}
public void setEnableAnimation(boolean enableAnimation) {
this.enableAnimation = enableAnimation;
Settings.save();
public void setHasProxy(boolean hasProxy) {
this.hasProxy = hasProxy;
Settings.INSTANCE.save();
}
public boolean isEnableBlur() {
return enableBlur;
public boolean isHasProxyAuth() {
return hasProxyAuth;
}
public void setEnableBlur(boolean enableBlur) {
this.enableBlur = enableBlur;
Settings.save();
public void setHasProxyAuth(boolean hasProxyAuth) {
this.hasProxyAuth = hasProxyAuth;
Settings.INSTANCE.save();
}
/**
* Last selected profile name.
*/
public String getLast() {
if (last == null)
last = Settings.DEFAULT_PROFILE;
return last;
public int getProxyType() {
return proxyType;
}
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 Map getAuthenticatorConfig(String authId) {
return auth.get(authId);
}
public void setAuthenticatorConfig(String authId, Map map) {
auth.put(authId, map);
Settings.save();
}
public DownloadType getDownloadSource() {
if (downloadtype >= DownloadType.values().length || downloadtype < 0) {
downloadtype = 0;
Settings.save();
}
return DownloadType.values()[downloadtype];
public void setProxyType(int proxyType) {
this.proxyType = proxyType;
Settings.INSTANCE.save();
}
public String getProxyHost() {
return proxyHost == null ? "" : proxyHost;
return proxyHost;
}
public void setProxyHost(String proxyHost) {
this.proxyHost = proxyHost;
Settings.save();
Settings.INSTANCE.save();
}
public String getProxyPort() {
return proxyPort == null ? "" : proxyPort;
return proxyPort;
}
public void setProxyPort(String proxyPort) {
this.proxyPort = proxyPort;
Settings.save();
Settings.INSTANCE.save();
}
public String getProxyUserName() {
return proxyUserName == null ? "" : proxyUserName;
public String getProxyUser() {
return proxyUser;
}
public void setProxyUserName(String proxyUserName) {
this.proxyUserName = proxyUserName;
Settings.save();
public void setProxyUser(String proxyUser) {
this.proxyUser = proxyUser;
Settings.INSTANCE.save();
}
public String getProxyPassword() {
return proxyPassword == null ? "" : proxyPassword;
public String getProxyPass() {
return proxyPass;
}
public void setProxyPassword(String proxyPassword) {
this.proxyPassword = proxyPassword;
Settings.save();
public void setProxyPass(String proxyPass) {
this.proxyPass = proxyPass;
Settings.INSTANCE.save();
}
public int getFontSize() {
return fontSize;
public String getTheme() {
return theme;
}
public void setFontSize(int fontSize) {
this.fontSize = fontSize;
Settings.save();
}
public Font getConsoleFont() {
return Font.decode(fontFamily + "-" + fontSize);
public void setTheme(String theme) {
this.theme = theme;
Settings.INSTANCE.save();
}
public String getFontFamily() {
return Font.decode(fontFamily).getFamily();
public List<JavaVersion> getJava() {
return java;
}
public void setFontFamily(String fontFamily) {
this.fontFamily = Font.decode(fontFamily).getFamily(); // avoid invalid font family
Settings.save();
public void setJava(List<JavaVersion> java) {
this.java = java;
Settings.INSTANCE.save();
}
public String getLocalization() {
return localization;
}
public void setLocalization(String localization) {
this.localization = localization;
Settings.save();
Settings.INSTANCE.save();
}
public int getDownloadType() {
return downloadType;
}
public void setDownloadType(int downloadType) {
this.downloadType = downloadType;
Settings.INSTANCE.save();
}
public Map<String, Profile> getConfigurations() {
return configurations;
}
public void setConfigurations(Map<String, Profile> configurations) {
this.configurations = configurations;
Settings.INSTANCE.save();
}
public List<Map<Object, Object>> getAccounts() {
return accounts;
}
public void setAccounts(List<Map<Object, Object>> accounts) {
this.accounts = accounts;
Settings.INSTANCE.save();
}
public String getSelectedAccount() {
return selectedAccount;
}
public void setSelectedAccount(String selectedAccount) {
this.selectedAccount = selectedAccount;
Settings.INSTANCE.save();
}
public String getFontFamily() {
return fontFamily;
}
public void setFontFamily(String fontFamily) {
this.fontFamily = fontFamily;
Settings.INSTANCE.save();
}
public double getFontSize() {
return fontSize;
}
public void setFontSize(double fontSize) {
this.fontSize = fontSize;
Settings.INSTANCE.save();
}
public int getLogLines() {
if (logLines == 100 || logLines == 1000 || logLines == 5000)
return logLines;
else
return logLines = 100;
return logLines;
}
public void setLogLines(int logLines) {
this.logLines = logLines;
Settings.save();
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
public Set<String> getAuthlibInjectorServerURLs() {
return authlibInjectorServerURLs;
}
public void setAuthlibInjectorServerURLs(Set<String> authlibInjectorServerURLs) {
this.authlibInjectorServerURLs = authlibInjectorServerURLs;
Settings.INSTANCE.save();
}
}
@@ -0,0 +1,36 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.util.Lang;
import java.util.Arrays;
import java.util.List;
public final class DownloadProviders {
private DownloadProviders() {}
public static final List<DownloadProvider> DOWNLOAD_PROVIDERS = Lang.immutableListOf(MojangDownloadProvider.INSTANCE, BMCLAPIDownloadProvider.INSTANCE);
public static DownloadProvider getDownloadProvider(int index) {
return Lang.get(DOWNLOAD_PROVIDERS, index).orElse(MojangDownloadProvider.INSTANCE);
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,16 +15,16 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.util.task;
package org.jackhuang.hmcl.setting;
/**
*
* @author huangyuhui
*/
public class NoShownTaskException extends RuntimeException {
private static final long serialVersionUID = 4893571368018439312L;
public enum EnumBackgroundImage {
DEFAULT,
CUSTOM;
public NoShownTaskException(String msg) {
super(msg);
public static EnumBackgroundImage indexOf(int index) {
if (index >= values().length || index < 0)
return DEFAULT;
else
return values()[index];
}
}
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
*
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -15,18 +15,24 @@
* 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.auth;
package org.jackhuang.hmcl.setting;
/**
* Determines where game runs in and game files such as mods.
*
* @author huangyuhui
*/
public final class LoginInfo {
public String username, password;
public LoginInfo(String username, String password) {
this.username = username;
this.password = password;
}
}
public enum EnumGameDirectory {
/**
* .minecraft
*/
ROOT_FOLDER,
/**
* .minecraft/versions/&lt;version name&gt;
*/
VERSION_FOLDER,
/**
* user customized directory.
*/
CUSTOM
}
@@ -0,0 +1,28 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import javafx.scene.text.Font;
import java.util.List;
public final class Fonts {
private Fonts() {}
public static final List<String> FONTS = Font.getFamilies();
}
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui
*
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -15,10 +15,10 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.util;
package org.jackhuang.hmcl.setting;
/**
*
* The visibility of launcher.
* @author huangyuhui
*/
public enum LauncherVisibility {
@@ -28,15 +28,18 @@ public enum LauncherVisibility {
* launch game.
*/
CLOSE,
/**
* Hide the launcher when the game process created, if failed to launch
* game, will show the log window.
*/
HIDE,
/**
* Keep the launcher visible even if the game launched successfully.
*/
KEEP,
/**
* Hide the launcher and reopen it when game closes.
*/
@@ -0,0 +1,113 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.ui.construct.UTF8Control;
import org.jackhuang.hmcl.util.Lang;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
public final class Locales {
public static final SupportedLocale DEFAULT = new SupportedLocale(Locale.getDefault(), "lang.default");
/**
* English
*/
public static final SupportedLocale EN = new SupportedLocale(Locale.ROOT);
/**
* Traditional Chinese
*/
public static final SupportedLocale ZH = new SupportedLocale(Locale.TRADITIONAL_CHINESE);
/**
* Simplified Chinese
*/
public static final SupportedLocale ZH_CN = new SupportedLocale(Locale.SIMPLIFIED_CHINESE);
/**
* Vietnamese
*/
public static final SupportedLocale VI = new SupportedLocale(new Locale("vi"));
/**
* Russian
*/
public static final SupportedLocale RU = new SupportedLocale(new Locale("ru"));
public static final List<SupportedLocale> LOCALES = Lang.immutableListOf(DEFAULT, EN, ZH_CN);
public static SupportedLocale getLocale(int index) {
return Lang.get(LOCALES, index).orElse(DEFAULT);
}
public static SupportedLocale getLocaleByName(String name) {
if (name == null) return DEFAULT;
switch (name.toLowerCase()) {
case "en": return EN;
case "zh": return ZH;
case "zh_cn": return ZH_CN;
case "vi": return VI;
case "ru": return RU;
default: return DEFAULT;
}
}
public static String getNameByLocale(SupportedLocale locale) {
if (locale == EN) return "en";
else if (locale == ZH) return "zh";
else if (locale == ZH_CN) return "zh_CN";
else if (locale == VI) return "vi";
else if (locale == RU) return "ru";
else if (locale == DEFAULT) return "def";
else throw new IllegalArgumentException("Unknown locale: " + locale);
}
public static class SupportedLocale {
private final Locale locale;
private final String name;
private final ResourceBundle resourceBundle;
SupportedLocale(Locale locale) {
this(locale, null);
}
SupportedLocale(Locale locale, String name) {
this.locale = locale;
this.name = name;
resourceBundle = ResourceBundle.getBundle("assets.lang.I18N", locale, UTF8Control.INSTANCE);
}
public Locale getLocale() {
return locale;
}
public ResourceBundle getResourceBundle() {
return resourceBundle;
}
public String getName(ResourceBundle newResourceBundle) {
if (name == null) return resourceBundle.getString("lang");
else return newResourceBundle.getString(name);
}
}
}
+123 -121
View File
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui
* Copyright (C) 2018 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
@@ -17,167 +17,169 @@
*/
package org.jackhuang.hmcl.setting;
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 com.google.gson.*;
import javafx.beans.InvalidationListener;
import org.jackhuang.hmcl.game.HMCLDependencyManager;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.util.ImmediateObjectProperty;
import org.jackhuang.hmcl.util.ImmediateStringProperty;
import java.io.File;
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;
import java.lang.reflect.Type;
import java.util.Optional;
/**
*
* @author huangyuhui
*/
public final class Profile implements IProfile {
public final class Profile {
@SerializedName("selectedMinecraftVersion")
private String selectedVersion = "";
@SerializedName("gameDir")
private String gameDir;
@SerializedName("noCommon")
private boolean noCommon;
@SerializedName("global")
private VersionSetting global;
private final HMCLGameRepository repository;
private final ModManager modManager;
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<>();
private final ImmediateObjectProperty<File> gameDirProperty;
public ImmediateObjectProperty<File> gameDirProperty() {
return gameDirProperty;
}
public File getGameDir() {
return gameDirProperty.get();
}
public void setGameDir(File gameDir) {
gameDirProperty.set(gameDir);
}
private final ImmediateObjectProperty<VersionSetting> globalProperty = new ImmediateObjectProperty<>(this, "global", new VersionSetting());
public ImmediateObjectProperty<VersionSetting> globalProperty() {
return globalProperty;
}
public VersionSetting getGlobal() {
return globalProperty.get();
}
public void setGlobal(VersionSetting global) {
if (global == null)
global = new VersionSetting();
globalProperty.set(global);
}
private final ImmediateStringProperty nameProperty;
public ImmediateStringProperty nameProperty() {
return nameProperty;
}
public String getName() {
return nameProperty.get();
}
public void setName(String name) {
nameProperty.set(name);
}
public Profile() {
this("Default");
}
public Profile(String name) {
this(name, ".minecraft");
this(name, new File(".minecraft"));
}
public Profile(String name, String gameDir) {
this.name = name;
this.gameDir = gameDir;
this.global = new VersionSetting();
public Profile(String name, File initialGameDir) {
nameProperty = new ImmediateStringProperty(this, "name", name);
gameDirProperty = new ImmediateObjectProperty<>(this, "gameDir", initialGameDir);
repository = new HMCLGameRepository(this, initialGameDir);
modManager = new ModManager(repository);
gameDirProperty.addListener((a, b, newValue) -> repository.changeDirectory(newValue));
}
public Profile(String name, Profile v) {
this();
if (v == null)
return;
this.name = name;
gameDir = v.gameDir;
public HMCLGameRepository getRepository() {
return repository;
}
@Override
public HMCLMinecraftService service() {
return service;
public ModManager getModManager() {
return modManager;
}
public HMCLGameLauncher launcher() {
return launcher;
}
public VersionSetting getSelectedVersionSetting() {
return getVersionSetting(getSelectedVersion());
public HMCLDependencyManager getDependency() {
return new HMCLDependencyManager(this, Settings.INSTANCE.getDownloadProvider(), Settings.INSTANCE.getProxy());
}
public VersionSetting getVersionSetting(String id) {
VersionSetting vs = service().getVersionSetting(id);
VersionSetting vs = repository.getVersionSetting(id);
if (vs == null || vs.isUsesGlobal()) {
global.isGlobal = true;
global.id = id;
return global;
getGlobal().setGlobal(true); // always keep global.isGlobal = true
return getGlobal();
} else
return vs;
}
public boolean isVersionSettingGlobe(String id) {
VersionSetting vs = service().getVersionSetting(id);
public boolean isVersionGlobal(String id) {
VersionSetting vs = repository.getVersionSetting(id);
return vs == null || vs.isUsesGlobal();
}
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));
}
public void makeVersionSettingGlobal(String id) {
HMCLMinecraftService s = (HMCLMinecraftService) service();
VersionSetting vs = s.getVersionSetting(id);
/**
* 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 = repository.getVersionSetting(id);
if (vs == null)
return;
vs.setUsesGlobal(true);
propertyChanged.fire(new PropertyChangedEvent<>(this, "selectedVersion", selectedVersion, selectedVersion));
vs = repository.createVersionSetting(id);
if (vs == null)
return null;
vs.setUsesGlobal(false);
return vs;
}
@Override
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);
public void globalizeVersionSetting(String id) {
VersionSetting vs = repository.getVersionSetting(id);
if (vs != null)
vs.setUsesGlobal(true);
}
public void addPropertyChangedListener(InvalidationListener listener) {
nameProperty.addListener(listener);
globalProperty.addListener(listener);
gameDirProperty.addListener(listener);
}
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
public static final Serializer INSTANCE = new Serializer();
private Serializer() {
}
return StrUtils.isBlank(v) ? null : v;
}
@Override
public void setSelectedVersion(String newVersion) {
PropertyChangedEvent event = new PropertyChangedEvent<>(this, "selectedVersion", this.selectedVersion, newVersion);
this.selectedVersion = newVersion;
propertyChanged.fire(event);
}
@Override
public JsonElement serialize(Profile src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null)
return JsonNull.INSTANCE;
@Override
public File getGameDir() {
if (StrUtils.isBlank(gameDir))
gameDir = MCUtils.getInitGameDir().getPath();
return new File(gameDir.replace('\\', '/'));
}
JsonObject jsonObject = new JsonObject();
jsonObject.add("global", context.serialize(src.getGlobal()));
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
@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);
}
return jsonObject;
}
public boolean isNoCommon() {
return noCommon;
}
@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("");
public void setNoCommon(boolean noCommon) {
PropertyChangedEvent event = new PropertyChangedEvent(this, "noCommon", this.noCommon, noCommon);
this.noCommon = noCommon;
propertyChanged.fire(event);
}
Profile profile = new Profile("Default", new File(gameDir));
profile.setGlobal(context.deserialize(obj.get("global"), VersionSetting.class));
return profile;
}
@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();
}
}
@@ -0,0 +1,36 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.Main;
public final class Profiles {
private Profiles() {
}
public static String getProfileDisplayName(Profile profile) {
switch (profile.getName()) {
case Settings.DEFAULT_PROFILE:
return Main.i18n("profile.default");
case Settings.HOME_PROFILE:
return Main.i18n("profile.home");
default:
return profile.getName();
}
}
}
@@ -0,0 +1,37 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.util.Lang;
import java.net.Proxy;
import java.util.Arrays;
import java.util.List;
/**
* @author huangyuhui
*/
public final class Proxies {
private Proxies() {}
public static final List<Proxy.Type> PROXIES = Lang.immutableListOf(Proxy.Type.DIRECT, Proxy.Type.HTTP, Proxy.Type.SOCKS);
public static Proxy.Type getProxyType(int index) {
return Lang.get(PROXIES, index).orElse(null);
}
}
+481 -101
View File
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui
*
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -17,156 +17,536 @@
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.JsonSyntaxException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.ProfileChangedEvent;
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.util.*;
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.UpdateChecker;
import org.jackhuang.hmcl.util.sys.IOUtils;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
/**
*
* @author huangyuhui
*/
public final class Settings {
public class Settings {
public static final Gson GSON = new GsonBuilder()
.registerTypeAdapter(VersionSetting.class, VersionSetting.Serializer.INSTANCE)
.registerTypeAdapter(Profile.class, Profile.Serializer.INSTANCE)
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
.setPrettyPrinting().create();
public static final String DEFAULT_PROFILE = "Default";
public static final String HOME_PROFILE = "Home";
public static final File SETTINGS_FILE = new File("hmcl.json").getAbsoluteFile();
private static final Config SETTINGS;
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(HMCLApi.HMCL_VERSION, "hmcl");
public static final Settings INSTANCE = new Settings();
public static Config getInstance() {
return SETTINGS;
}
private 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));
private final Config SETTINGS = initSettings();
for (Map.Entry<String, Profile> entry : getProfiles().entrySet()) {
Profile e = entry.getValue();
e.setName(entry.getKey());
e.propertyChanged.register(Settings::save);
private Map<String, Account> accounts = new HashMap<>();
{
loadProxy();
for (Iterator<Map<Object, Object>> iterator = SETTINGS.getAccounts().iterator(); iterator.hasNext(); ) {
Map<Object, Object> settings = iterator.next();
AccountFactory<?> factory = Accounts.ACCOUNT_FACTORY.get(Lang.get(settings, "type", String.class).orElse(""));
if (factory == null) {
// unrecognized account type, so remove it.
iterator.remove();
continue;
}
Account account;
try {
account = factory.fromStorage(settings, getProxy());
} catch (Exception e) {
// storage is malformed, delete.
iterator.remove();
continue;
}
accounts.put(Accounts.getAccountId(account), account);
}
checkAuthlibInjectorServerURLs();
checkAuthlibInjectorAccounts();
checkProfileMap();
save();
for (Map.Entry<String, Profile> entry2 : getProfileMap().entrySet()) {
entry2.getValue().setName(entry2.getKey());
entry2.getValue().nameProperty().setChangedListener(this::profileNameChanged);
entry2.getValue().addPropertyChangedListener(e -> save());
}
Lang.ignoringException(() -> Runtime.getRuntime().addShutdownHook(new Thread(this::save)));
}
private static Config initSettings() {
private 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.");
String str = FileUtils.readText(SETTINGS_FILE);
if (StringUtils.isBlank(str))
Logging.LOG.finer("Settings file is empty, use the default settings.");
else {
Config d = C.GSON.fromJson(str, Config.class);
Config d = 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);
Logging.LOG.finest("Initialized settings.");
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Something happened wrongly when load settings.", e);
}
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() {
public void save() {
try {
FileUtils.write(SETTINGS_FILE, C.GSON.toJson(SETTINGS), IOUtils.DEFAULT_CHARSET);
SETTINGS.getAccounts().clear();
for (Account account : accounts.values()) {
Map<Object, Object> storage = account.toStorage();
storage.put("type", Accounts.getAccountType(account));
SETTINGS.getAccounts().add(storage);
}
FileUtils.writeText(SETTINGS_FILE, GSON.toJson(SETTINGS));
} catch (IOException ex) {
HMCLog.err("Failed to save config", ex);
Logging.LOG.log(Level.SEVERE, "Failed to save config", ex);
}
}
public static Profile getLastProfile() {
if (!hasProfile(getInstance().getLast()))
getInstance().setLast(DEFAULT_PROFILE);
return getProfile(getInstance().getLast());
private final StringProperty commonPath = new ImmediateStringProperty(this, "commonPath", SETTINGS.getCommonDirectory()) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.setCommonDirectory(get());
}
};
public String getCommonPath() {
return commonPath.get();
}
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 StringProperty commonPathProperty() {
return commonPath;
}
public static boolean hasProfile(String name) {
if (name == null)
name = DEFAULT_PROFILE;
return getProfiles().containsKey(name);
public void setCommonPath(String commonPath) {
this.commonPath.set(commonPath);
}
public static Map<String, Profile> getProfiles() {
private Locales.SupportedLocale locale = Locales.getLocaleByName(SETTINGS.getLocalization());
public Locales.SupportedLocale getLocale() {
return locale;
}
public void setLocale(Locales.SupportedLocale locale) {
this.locale = locale;
SETTINGS.setLocalization(Locales.getNameByLocale(locale));
}
private Proxy proxy = Proxy.NO_PROXY;
public Proxy getProxy() {
return proxy;
}
private Proxy.Type proxyType = Proxies.getProxyType(SETTINGS.getProxyType());
public Proxy.Type getProxyType() {
return proxyType;
}
public void setProxyType(Proxy.Type proxyType) {
this.proxyType = proxyType;
SETTINGS.setProxyType(Proxies.PROXIES.indexOf(proxyType));
loadProxy();
}
public String getProxyHost() {
return SETTINGS.getProxyHost();
}
public void setProxyHost(String proxyHost) {
SETTINGS.setProxyHost(proxyHost);
}
public String getProxyPort() {
return SETTINGS.getProxyPort();
}
public void setProxyPort(String proxyPort) {
SETTINGS.setProxyPort(proxyPort);
}
public String getProxyUser() {
return SETTINGS.getProxyUser();
}
public void setProxyUser(String proxyUser) {
SETTINGS.setProxyUser(proxyUser);
}
public String getProxyPass() {
return SETTINGS.getProxyPass();
}
public void setProxyPass(String proxyPass) {
SETTINGS.setProxyPass(proxyPass);
}
public boolean hasProxy() { return SETTINGS.isHasProxy(); }
public void setHasProxy(boolean hasProxy) { SETTINGS.setHasProxy(hasProxy); }
public boolean hasProxyAuth() { return SETTINGS.isHasProxyAuth(); }
public void setHasProxyAuth(boolean hasProxyAuth) { SETTINGS.setHasProxyAuth(hasProxyAuth); }
private void loadProxy() {
String host = getProxyHost();
Integer port = Lang.toIntOrNull(getProxyPort());
if (!hasProxy() || StringUtils.isBlank(host) || port == null || getProxyType() == Proxy.Type.DIRECT)
proxy = Proxy.NO_PROXY;
else {
System.setProperty("http.proxyHost", getProxyHost());
System.setProperty("http.proxyPort", getProxyPort());
proxy = new Proxy(proxyType, new InetSocketAddress(host, port));
String user = getProxyUser();
String pass = getProxyPass();
if (hasProxyAuth() && StringUtils.isNotBlank(user) && StringUtils.isNotBlank(pass)) {
System.setProperty("http.proxyUser", user);
System.setProperty("http.proxyPassword", pass);
Authenticator.setDefault(new Authenticator() {
@Override
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(user, pass.toCharArray());
}
});
}
}
}
public Font getFont() {
return Font.font(SETTINGS.getFontFamily(), SETTINGS.getFontSize());
}
public void setFont(Font font) {
SETTINGS.setFontFamily(font.getFamily());
SETTINGS.setFontSize(font.getSize());
}
public int getLogLines() {
return Math.max(SETTINGS.getLogLines(), 100);
}
public void setLogLines(int logLines) {
SETTINGS.setLogLines(logLines);
}
public Set<String> getAuthlibInjectorServerURLs() {
return SETTINGS.getAuthlibInjectorServerURLs();
}
public void removeAuthlibInjectorServerURL(String serverURL) {
checkAuthlibInjectorServerURLs();
SETTINGS.getAuthlibInjectorServerURLs().remove(serverURL);
checkAuthlibInjectorAccounts();
save();
}
public void addAuthlibInjectorServerURL(String serverURL) {
checkAuthlibInjectorServerURLs();
SETTINGS.getAuthlibInjectorServerURLs().add(serverURL);
save();
}
private void checkAuthlibInjectorServerURLs() {
if (SETTINGS.getAuthlibInjectorServerURLs() == null)
SETTINGS.setAuthlibInjectorServerURLs(new HashSet<>());
}
private void checkAuthlibInjectorAccounts() {
for (Account account : getAccounts()) {
if (account instanceof AuthlibInjectorAccount) {
AuthlibInjectorAccount injectorAccount = (AuthlibInjectorAccount) account;
if (!SETTINGS.getAuthlibInjectorServerURLs().contains(injectorAccount.getServerBaseURL()))
deleteAccount(account);
}
}
}
public DownloadProvider getDownloadProvider() {
switch (SETTINGS.getDownloadType()) {
case 0:
return MojangDownloadProvider.INSTANCE;
case 1:
return BMCLAPIDownloadProvider.INSTANCE;
default:
return MojangDownloadProvider.INSTANCE;
}
}
public void setDownloadProvider(DownloadProvider downloadProvider) {
if (downloadProvider == MojangDownloadProvider.INSTANCE)
SETTINGS.setDownloadType(0);
else if (downloadProvider == BMCLAPIDownloadProvider.INSTANCE)
SETTINGS.setDownloadType(1);
else
throw new IllegalArgumentException("Unknown download provider: " + downloadProvider);
}
/****************************************
* ACCOUNTS *
****************************************/
private final ImmediateObjectProperty<Account> selectedAccount = new ImmediateObjectProperty<Account>(this, "selectedAccount", accounts.get(SETTINGS.getSelectedAccount())) {
@Override
public Account get() {
Account a = super.get();
if (a == null || !accounts.containsKey(Accounts.getAccountId(a))) {
Account acc = accounts.values().stream().findAny().orElse(null);
set(acc);
return acc;
} else return a;
}
@Override
public void set(Account newValue) {
if (newValue == null || accounts.containsKey(Accounts.getAccountId(newValue))) {
super.set(newValue);
}
}
@Override
public void invalidated() {
super.invalidated();
SETTINGS.setSelectedAccount(getValue() == null ? "" : Accounts.getAccountId(getValue()));
}
};
public Account getSelectedAccount() {
return selectedAccount.get();
}
public ObjectProperty<Account> selectedAccountProperty() {
return selectedAccount;
}
public void setSelectedAccount(Account selectedAccount) {
this.selectedAccount.set(selectedAccount);
}
public void addAccount(Account account) {
accounts.put(Accounts.getAccountId(account), account);
}
public Account getAccount(String name, String character) {
return accounts.get(Accounts.getAccountId(name, character));
}
public Collection<Account> getAccounts() {
return Collections.unmodifiableCollection(accounts.values());
}
public void deleteAccount(String name, String character) {
accounts.remove(Accounts.getAccountId(name, character));
selectedAccount.get();
}
public void deleteAccount(Account account) {
accounts.remove(Accounts.getAccountId(account));
selectedAccount.get();
}
/****************************************
* BACKGROUND *
****************************************/
private final ImmediateStringProperty backgroundImage = new ImmediateStringProperty(this, "backgroundImage", SETTINGS.getBackgroundImage()) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.setBackgroundImage(get());
}
};
public String getBackgroundImage() {
return backgroundImage.get();
}
public ImmediateStringProperty backgroundImageProperty() {
return backgroundImage;
}
public void setBackgroundImage(String backgroundImage) {
this.backgroundImage.set(backgroundImage);
}
private final ImmediateObjectProperty<EnumBackgroundImage> backgroundImageType = new ImmediateObjectProperty<EnumBackgroundImage>(this, "backgroundImageType", EnumBackgroundImage.indexOf(SETTINGS.getBackgroundImageType())) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.setBackgroundImageType(get().ordinal());
}
};
public EnumBackgroundImage getBackgroundImageType() {
return backgroundImageType.get();
}
public ImmediateObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {
return backgroundImageType;
}
public void setBackgroundImageType(EnumBackgroundImage backgroundImageType) {
this.backgroundImageType.set(backgroundImageType);
}
/****************************************
* THEME *
****************************************/
private final ImmediateObjectProperty<Theme> theme = new ImmediateObjectProperty<Theme>(this, "theme", Theme.getTheme(SETTINGS.getTheme()).orElse(Theme.BLUE)) {
@Override
public void invalidated() {
super.invalidated();
SETTINGS.setTheme(get().getName().toLowerCase());
}
};
public Theme getTheme() {
return theme.get();
}
public void setTheme(Theme theme) {
this.theme.set(theme);
}
public ImmediateObjectProperty<Theme> themeProperty() {
return theme;
}
/****************************************
* PROFILES *
****************************************/
public Profile getSelectedProfile() {
checkProfileMap();
if (!hasProfile(SETTINGS.getSelectedProfile())) {
SETTINGS.setSelectedProfile(getProfileMap().keySet().stream().findFirst().get());
Schedulers.computation().schedule(this::onProfileChanged);
}
return getProfile(SETTINGS.getSelectedProfile());
}
public void setSelectedProfile(Profile selectedProfile) {
if (hasProfile(selectedProfile.getName()) && !Objects.equals(selectedProfile.getName(), SETTINGS.getSelectedProfile())) {
SETTINGS.setSelectedProfile(selectedProfile.getName());
Schedulers.computation().schedule(this::onProfileChanged);
}
}
public Profile getProfile(String name) {
checkProfileMap();
Optional<Profile> p = name == null ? getProfileMap().values().stream().findFirst() : Optional.ofNullable(getProfileMap().get(name));
return p.orElse(null);
}
public boolean hasProfile(String name) {
return getProfileMap().containsKey(name);
}
public Map<String, Profile> getProfileMap() {
return SETTINGS.getConfigurations();
}
public static Collection<Profile> getProfilesFiltered() {
return CollectionUtils.filter(getProfiles().values(), t -> t != null && t.getName() != null);
public Collection<Profile> getProfiles() {
return getProfileMap().values().stream().filter(t -> StringUtils.isNotBlank(t.getName())).collect(Collectors.toList());
}
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 void putProfile(Profile ver) {
if (StringUtils.isBlank(ver.getName()))
throw new IllegalArgumentException("Profile's name is empty");
getProfileMap().put(ver.getName(), ver);
ver.nameProperty().setChangedListener(this::profileNameChanged);
save();
}
public static boolean delProfile(Profile ver) {
return delProfile(ver.getName());
public void deleteProfile(Profile profile) {
deleteProfile(profile.getName());
}
public static boolean delProfile(String ver) {
if (DEFAULT_PROFILE.equals(ver)) {
MessageBox.show(C.i18n("settings.cannot_remove_default_config"));
return false;
public void deleteProfile(String profileName) {
getProfileMap().remove(profileName);
checkProfileMap();
Schedulers.computation().schedule(this::onProfileLoading);
}
private void checkProfileMap() {
if (getProfileMap().isEmpty()) {
getProfileMap().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE));
getProfileMap().put(HOME_PROFILE, new Profile(HOME_PROFILE, Main.MINECRAFT_DIRECTORY));
}
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();
private void onProfileChanged() {
getSelectedProfile().getRepository().refreshVersionsAsync().start();
EventBus.EVENT_BUS.fireEvent(new ProfileChangedEvent(SETTINGS, getSelectedProfile()));
}
public static void onProfileLoading() {
HMCLApi.EVENT_BUS.fireChannel(new ProfileLoadingEvent(SETTINGS));
private void profileNameChanged(ObservableValue<? extends String> observableValue, String oldValue, String newValue) {
getProfileMap().put(newValue, getProfileMap().remove(oldValue));
}
/**
* Start profiles loading process.
* Invoked by loading GUI phase.
*/
public void onProfileLoading() {
EventBus.EVENT_BUS.fireEvent(new ProfileLoadingEvent(SETTINGS));
onProfileChanged();
}
}
@@ -0,0 +1,108 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.setting;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.IOUtils;
import org.jackhuang.hmcl.util.Logging;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.logging.Level;
public class Theme {
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
public static final Theme DARK_BLUE = new Theme("dark_blue", "#283593");
public static final Theme GREEN = new Theme("green", "#43A047");
public static final Theme ORANGE = new Theme("orange", "#E67E22");
public static final Theme PURPLE = new Theme("purple", "#9C27B0");
public static final Theme RED = new Theme("red", "#F44336");
public static final Theme[] VALUES = new Theme[]{BLUE, DARK_BLUE, GREEN, ORANGE, PURPLE, 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 String[] getStylesheets() {
String name = isCustom() ? BLUE.getName() : this.name;
String css = Theme.class.getResource("/assets/css/" + name + ".css").toExternalForm();
try {
File temp = File.createTempFile("hmcl", ".css");
FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css")).replace("%base-color%", color));
css = temp.toURI().toString();
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet", e);
}
return new String[]{
Theme.class.getResource("/css/jfoenix-fonts.css").toExternalForm(),
Theme.class.getResource("/css/jfoenix-design.css").toExternalForm(),
css,
Theme.class.getResource("/assets/css/root.css").toExternalForm()
};
}
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();
for (Theme theme : VALUES)
if (theme.name.equalsIgnoreCase(name))
return Optional.of(theme);
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;
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,325 +17,573 @@
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.annotations.SerializedName;
import java.io.File;
import com.google.gson.*;
import javafx.beans.InvalidationListener;
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;
import org.jackhuang.hmcl.game.LaunchOptions;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Optional;
/**
*
* @author huangyuhui
*/
public class VersionSetting {
public final class VersionSetting {
public transient String id;
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;
private boolean global = false;
public boolean isGlobal() {
return global;
}
public void setGlobal(boolean global) {
this.global = global;
}
private final ImmediateBooleanProperty usesGlobalProperty = new ImmediateBooleanProperty(this, "usesGlobal", false);
public ImmediateBooleanProperty 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 ImmediateStringProperty javaProperty = new ImmediateStringProperty(this, "java", "");
public ImmediateStringProperty javaProperty() {
return javaProperty;
}
/**
* Java version or null if user customizes java directory.
*/
public String getJava() {
return javaProperty.get();
}
public void setJava(String java) {
javaProperty.set(java);
}
private final ImmediateStringProperty javaDirProperty = new ImmediateStringProperty(this, "javaDir", "");
public ImmediateStringProperty 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 ImmediateStringProperty wrapperProperty = new ImmediateStringProperty(this, "wrapper", "");
public ImmediateStringProperty 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 ImmediateStringProperty permSizeProperty = new ImmediateStringProperty(this, "permSize", "");
public ImmediateStringProperty 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 ImmediateIntegerProperty maxMemoryProperty = new ImmediateIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY);
public ImmediateIntegerProperty 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 ImmediateObjectProperty<Integer> minMemoryProperty = new ImmediateObjectProperty<>(this, "minMemory", null);
public ImmediateObjectProperty<Integer> minMemoryProperty() {
return minMemoryProperty;
}
public Integer getMinMemory() {
return minMemoryProperty.get();
}
public void setMinMemory(Integer minMemory) {
minMemoryProperty.set(minMemory);
}
private final ImmediateStringProperty preLaunchCommandProperty = new ImmediateStringProperty(this, "precalledCommand", "");
public ImmediateStringProperty 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 ImmediateStringProperty javaArgsProperty = new ImmediateStringProperty(this, "javaArgs", "");
public ImmediateStringProperty 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 ImmediateStringProperty minecraftArgsProperty = new ImmediateStringProperty(this, "minecraftArgs", "");
public ImmediateStringProperty 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 ImmediateBooleanProperty noJVMArgsProperty = new ImmediateBooleanProperty(this, "noJVMArgs", false);
public ImmediateBooleanProperty 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 ImmediateBooleanProperty notCheckGameProperty = new ImmediateBooleanProperty(this, "notCheckGame", false);
public ImmediateBooleanProperty 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 ImmediateBooleanProperty noCommonProperty = new ImmediateBooleanProperty(this, "noCommon", false);
public ImmediateBooleanProperty noCommonProperty() {
return noCommonProperty;
}
/**
* True if HMCL does not find/download libraries in/to common path.
*/
public boolean isNoCommon() {
return noCommonProperty.get();
}
public void setNoCommon(boolean noCommon) {
noCommonProperty.set(noCommon);
}
private final ImmediateBooleanProperty showLogsProperty = new ImmediateBooleanProperty(this, "showLogs", false);
public ImmediateBooleanProperty 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 ImmediateStringProperty serverIpProperty = new ImmediateStringProperty(this, "serverIp", "");
public ImmediateStringProperty serverIpProperty() {
return serverIpProperty;
}
/**
* The server ip that will be entered after Minecraft successfully loaded immediately.
*
* Format: ip:port or without port.
*/
public String getServerIp() {
return serverIpProperty.get();
}
public void setServerIp(String serverIp) {
serverIpProperty.set(serverIp);
}
private final ImmediateBooleanProperty fullscreenProperty = new ImmediateBooleanProperty(this, "fullscreen", false);
public ImmediateBooleanProperty 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 ImmediateIntegerProperty widthProperty = new ImmediateIntegerProperty(this, "width", 854);
public ImmediateIntegerProperty 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 ImmediateIntegerProperty heightProperty = new ImmediateIntegerProperty(this, "height", 480);
public ImmediateIntegerProperty 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/&lt;version&gt;/<br/>
*/
private final ImmediateObjectProperty<EnumGameDirectory> gameDirTypeProperty = new ImmediateObjectProperty<>(this, "gameDirType", EnumGameDirectory.ROOT_FOLDER);
public ImmediateObjectProperty<EnumGameDirectory> gameDirTypeProperty() {
return gameDirTypeProperty;
}
public EnumGameDirectory getGameDirType() {
return gameDirTypeProperty.get();
}
public void setGameDirType(EnumGameDirectory gameDirType) {
gameDirTypeProperty.set(gameDirType);
}
/**
* Your custom gameDir
*/
private final ImmediateStringProperty gameDirProperty = new ImmediateStringProperty(this, "gameDir", "");
public ImmediateStringProperty gameDirProperty() {
return gameDirProperty;
}
public String getGameDir() {
return gameDirProperty.get();
}
public void setGameDir(String gameDir) {
gameDirProperty.set(gameDir);
}
// launcher settings
/**
* 0 - Close the launcher when the game starts.<br/>
* 1 - Hide the launcher when the game starts.<br/>
* 2 - Keep the launcher open.<br/>
*/
@SerializedName("launcherVisibility")
private int launcherVisibility;
private final ImmediateObjectProperty<LauncherVisibility> launcherVisibilityProperty = new ImmediateObjectProperty<>(this, "launcherVisibility", LauncherVisibility.HIDE);
/**
* 0 - .minecraft<br/>
* 1 - .minecraft/versions/&lt;version&gt;/<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 ImmediateObjectProperty<LauncherVisibility> launcherVisibilityProperty() {
return launcherVisibilityProperty;
}
public LauncherVisibility getLauncherVisibility() {
return LauncherVisibility.values()[launcherVisibility];
return launcherVisibilityProperty.get();
}
public void setLauncherVisibility(LauncherVisibility launcherVisibility) {
PropertyChangedEvent event = new PropertyChangedEvent(this, "launcherVisibility", this.launcherVisibility, launcherVisibility);
this.launcherVisibility = launcherVisibility.ordinal();
propertyChanged.fire(event);
launcherVisibilityProperty.set(launcherVisibility);
}
public GameDirType getGameDirType() {
if (gameDirType < 0 || gameDirType > 1)
setGameDirType(GameDirType.ROOT_FOLDER);
return GameDirType.values()[gameDirType];
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 ("Custom".equals(getJava())) {
try {
return JavaVersion.fromExecutable(new File(getJavaDir()));
} catch (IOException e) {
return null; // Custom Java Directory not found,
}
} else if (StringUtils.isNotBlank(getJava())) {
JavaVersion c = JavaVersion.getJREs().get(getJava());
if (c == null) {
setJava("Default");
return JavaVersion.fromCurrentEnvironment();
} else
return c;
} else throw new Error();
}
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);
noCommonProperty.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);
}
public String getPermSize() {
return permSize;
public LaunchOptions toLaunchOptions(File gameDir) throws InterruptedException {
JavaVersion javaVersion = Optional.ofNullable(getJavaVersion()).orElse(JavaVersion.fromCurrentEnvironment());
return new LaunchOptions.Builder()
.setGameDir(gameDir)
.setJava(javaVersion)
.setVersionName(Main.TITLE)
.setProfileName(Main.TITLE)
.setMinecraftArgs(getMinecraftArgs())
.setJavaArgs(getJavaArgs())
.setMaxMemory(getMaxMemory())
.setMinMemory(getMinMemory())
.setMetaspace(Lang.toIntOrNull(getPermSize()))
.setWidth(getWidth())
.setHeight(getHeight())
.setFullscreen(isFullscreen())
.setServerIp(getServerIp())
.setWrapper(getWrapper())
.setProxyHost(Settings.INSTANCE.getProxyHost())
.setProxyPort(Settings.INSTANCE.getProxyPort())
.setProxyUser(Settings.INSTANCE.getProxyUser())
.setProxyPass(Settings.INSTANCE.getProxyPass())
.setPrecalledCommand(getPreLaunchCommand())
.setNoGeneratedJVMArgs(isNoJVMArgs())
.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> {
public static final Serializer INSTANCE = new Serializer();
public boolean isNoJVMArgs() {
return noJVMArgs;
}
public void setNoJVMArgs(boolean noJVMArgs) {
PropertyChangedEvent event = new PropertyChangedEvent(this, "noJVMArgs", this.noJVMArgs, noJVMArgs);
this.noJVMArgs = noJVMArgs;
propertyChanged.fire(event);
}
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();
private Serializer() {
}
@Override
public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null) return JsonNull.INSTANCE;
JsonObject obj = new JsonObject();
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("noCommon", src.isNoCommon());
obj.addProperty("showLogs", src.isShowLogs());
obj.addProperty("gameDir", src.getGameDir());
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
obj.addProperty("gameDirType", src.getGameDirType().ordinal());
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.setNoCommon(Optional.ofNullable(obj.get("noCommon")).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)]);
return vs;
}
private int parseJsonPrimitive(JsonPrimitive primitive) {
return parseJsonPrimitive(primitive, 0);
}
private int parseJsonPrimitive(JsonPrimitive primitive, int defaultValue) {
if (primitive.isNumber())
return primitive.getAsInt();
else
return Lang.parseInt(primitive.getAsString(), defaultValue);
}
x.setJavaDir(str);
return x;
}
}
@@ -0,0 +1,132 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXProgressBar;
import com.jfoenix.controls.JFXRadioButton;
import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ToggleGroup;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.AccountHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
public final class AccountItem extends StackPane {
private final Account account;
@FXML
private Pane icon;
@FXML private VBox content;
@FXML private StackPane header;
@FXML private StackPane body;
@FXML private JFXButton btnDelete;
@FXML private JFXButton btnRefresh;
@FXML private Label lblUser;
@FXML private Label lblType;
@FXML private Label lblEmail;
@FXML private Label lblServer;
@FXML private Label lblCurrentAccount;
@FXML private JFXRadioButton chkSelected;
@FXML private JFXProgressBar pgsSkin;
@FXML private ImageView portraitView;
@FXML private HBox buttonPane;
public AccountItem(int i, Account account, ToggleGroup toggleGroup) {
this.account = account;
FXUtils.loadFXML(this, "/assets/fxml/account-item.fxml");
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -0.5, 1.0));
chkSelected.setToggleGroup(toggleGroup);
btnDelete.setGraphic(SVG.delete("black", 15, 15));
btnRefresh.setGraphic(SVG.refresh("black", 15, 15));
// create image view
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
chkSelected.getProperties().put("account", account);
setSelected(Settings.INSTANCE.getSelectedAccount() == account);
lblUser.setText(account.getCharacter());
lblType.setText(AccountsPage.accountType(account));
lblEmail.setText(account.getUsername());
if (account instanceof AuthlibInjectorAccount) {
Task.ofResult("serverName", () -> Accounts.getAuthlibInjectorServerName(((AuthlibInjectorAccount) account).getServerBaseURL()))
.subscribe(Schedulers.javafx(), variables -> lblServer.setText(variables.get("serverName")));
}
if (account instanceof YggdrasilAccount) {
btnRefresh.setOnMouseClicked(e -> {
pgsSkin.setVisible(true);
AccountHelper.refreshSkinAsync((YggdrasilAccount) account)
.subscribe(Schedulers.javafx(), this::loadSkin);
});
AccountHelper.loadSkinAsync((YggdrasilAccount) account)
.subscribe(Schedulers.javafx(), this::loadSkin);
} else
loadSkin();
if (account instanceof OfflineAccount) { // Offline Account cannot be refreshed,
buttonPane.getChildren().remove(btnRefresh);
}
}
private void loadSkin() {
pgsSkin.setVisible(false);
portraitView.setViewport(AccountHelper.getViewport(4));
if (account instanceof YggdrasilAccount)
portraitView.setImage(AccountHelper.getSkin((YggdrasilAccount) account, 4));
else
portraitView.setImage(AccountHelper.getDefaultSkin(account, 4));
FXUtils.limitSize(portraitView, 32, 32);
}
public Account getAccount() {
return account;
}
public void setSelected(boolean selected) {
lblCurrentAccount.setVisible(selected);
chkSelected.setSelected(selected);
}
public void setOnDeleteButtonMouseClicked(EventHandler<? super MouseEvent> eventHandler) {
btnDelete.setOnMouseClicked(eventHandler);
}
}
@@ -0,0 +1,92 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXDialog;
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 java.util.function.Consumer;
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;
private JFXDialog dialog;
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.ofResult("login", () -> {
try {
return oldAccount.logInWithPassword(password);
} catch (Exception e) {
return e;
}
}).subscribe(Schedulers.javafx(), variable -> {
Object account = variable.get("login");
if (account instanceof AuthInfo) {
success.accept(((AuthInfo) account));
dialog.close();
} else if (account instanceof NoSelectedCharacterException) {
dialog.close();
} else if (account instanceof Exception) {
lblCreationWarning.setText(AccountsPage.accountException((Exception) account));
}
progressBar.setVisible(false);
});
}
@FXML
private void onCancel() {
failed.run();
dialog.close();
}
public void setDialog(JFXDialog dialog) {
this.dialog = dialog;
}
}
@@ -0,0 +1,312 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.*;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.ToggleGroup;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.AccountHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.IconedItem;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;
public final class AccountsPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account"));
@FXML
private ScrollPane scrollPane;
@FXML private JFXMasonryPane masonryPane;
@FXML private JFXDialog dialog;
@FXML private JFXTextField txtUsername;
@FXML private JFXPasswordField txtPassword;
@FXML private Label lblCreationWarning;
@FXML private Label lblPassword;
@FXML private JFXComboBox<String> cboType;
@FXML private JFXComboBox<TwoLineListItem> cboServers;
@FXML private JFXProgressBar progressBar;
@FXML private Label lblAddInjectorServer;
@FXML private Hyperlink linkAddInjectorServer;
{
FXUtils.loadFXML(this, "/assets/fxml/account.fxml");
getChildren().remove(dialog);
dialog.setDialogContainer(this);
FXUtils.smoothScrolling(scrollPane);
cboType.getItems().setAll(Main.i18n("account.methods.offline"), Main.i18n("account.methods.yggdrasil"), Main.i18n("account.methods.authlib_injector"));
cboType.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> {
txtPassword.setVisible(newValue.intValue() != 0);
lblPassword.setVisible(newValue.intValue() != 0);
cboServers.setVisible(newValue.intValue() == 2);
linkAddInjectorServer.setVisible(newValue.intValue() == 2);
lblAddInjectorServer.setVisible(newValue.intValue() == 2);
});
cboType.getSelectionModel().select(0);
// These two lines can eliminate black, don't know why.
cboServers.getItems().setAll(new TwoLineListItem("", ""));
cboServers.getSelectionModel().select(0);
txtPassword.setOnAction(e -> onCreationAccept());
txtUsername.setOnAction(e -> onCreationAccept());
txtUsername.getValidators().add(new Validator(Main.i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), account -> {
for (Node node : masonryPane.getChildren())
if (node instanceof AccountItem)
((AccountItem) node).setSelected(account == ((AccountItem) node).getAccount());
});
loadAccounts();
loadServers();
if (Settings.INSTANCE.getAccounts().isEmpty())
addNewAccount();
}
public void loadAccounts() {
List<Node> children = new LinkedList<>();
int i = 0;
ToggleGroup group = new ToggleGroup();
for (Account account : Settings.INSTANCE.getAccounts()) {
children.add(buildNode(++i, account, group));
}
group.selectedToggleProperty().addListener((a, b, newValue) -> {
if (newValue != null)
Settings.INSTANCE.setSelectedAccount((Account) newValue.getProperties().get("account"));
});
FXUtils.resetChildren(masonryPane, children);
Platform.runLater(() -> {
masonryPane.requestLayout();
scrollPane.requestLayout();
});
}
public void loadServers() {
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
.map(serverURL -> new TwoLineListItem(Accounts.getAuthlibInjectorServerName(serverURL), serverURL))
.collect(Collectors.toList()))
.subscribe(Task.of(Schedulers.javafx(), variables -> {
cboServers.getItems().setAll(variables.<Collection<TwoLineListItem>>get("list"));
if (!cboServers.getItems().isEmpty())
cboServers.getSelectionModel().select(0);
}));
}
private Node buildNode(int i, Account account, ToggleGroup group) {
AccountItem item = new AccountItem(i, account, group);
item.setOnDeleteButtonMouseClicked(e -> {
Settings.INSTANCE.deleteAccount(account);
Platform.runLater(this::loadAccounts);
});
return item;
}
@FXML
private void addNewAccount() {
txtUsername.setText("");
txtPassword.setText("");
lblCreationWarning.setText("");
dialog.show();
}
@FXML
private void onAddInjecterServer() {
Controllers.navigate(Controllers.getServersPage());
}
@FXML
private void onCreationAccept() {
int type = cboType.getSelectionModel().getSelectedIndex();
String username = txtUsername.getText();
String password = txtPassword.getText();
String apiRoot = Optional.ofNullable(cboServers.getSelectionModel().getSelectedItem()).map(TwoLineListItem::getSubtitle).orElse(null);
progressBar.setVisible(true);
lblCreationWarning.setText("");
Task.ofResult("create_account", () -> {
AccountFactory<?> factory;
switch (type) {
case 0: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.OFFLINE_ACCOUNT_KEY); break;
case 1: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.YGGDRASIL_ACCOUNT_KEY); break;
case 2: factory = Accounts.ACCOUNT_FACTORY.get(Accounts.AUTHLIB_INJECTOR_ACCOUNT_KEY); break;
default: throw new Error();
}
return factory.create(new Selector(), username, password, apiRoot, Settings.INSTANCE.getProxy());
}).finalized(Schedulers.javafx(), variables -> {
Settings.INSTANCE.addAccount(variables.get("create_account"));
dialog.close();
loadAccounts();
progressBar.setVisible(false);
}, exception -> {
if (exception instanceof NoSelectedCharacterException) {
dialog.close();
} else {
lblCreationWarning.setText(accountException(exception));
}
progressBar.setVisible(false);
}).start();
}
@FXML
private void onCreationCancel() {
dialog.close();
}
public String getTitle() {
return title.get();
}
@Override
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public static String accountException(Exception exception) {
if (exception instanceof InvalidCredentialsException) {
return Main.i18n("account.failed.invalid_credentials");
} else if (exception instanceof NoCharacterException) {
return Main.i18n("account.failed.no_charactor");
} else if (exception instanceof ServerDisconnectException) {
return Main.i18n("account.failed.connect_authentication_server");
} else if (exception instanceof InvalidTokenException) {
return Main.i18n("account.failed.invalid_token");
} else if (exception instanceof InvalidPasswordException) {
return Main.i18n("account.failed.invalid_password");
} else {
return exception.getClass() + ": " + exception.getLocalizedMessage();
}
}
public static String accountType(Account account) {
if (account instanceof OfflineAccount) return Main.i18n("account.methods.offline");
else if (account instanceof AuthlibInjectorAccount) return Main.i18n("account.methods.authlib_injector");
else if (account instanceof YggdrasilAccount) return Main.i18n("account.methods.yggdrasil");
else throw new Error(Main.i18n("account.methods.no_method") + ": " + account);
}
private static 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;
{
setStyle("-fx-padding: 8px;");
cancel.setText(Main.i18n("button.cancel"));
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
cancel.setOnMouseClicked(e -> latch.countDown());
listBox.startCategory(Main.i18n("account.choose"));
setCenter(listBox);
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER_RIGHT);
hbox.getChildren().add(cancel);
setBottom(hbox);
}
@Override
public GameProfile select(Account account, List<GameProfile> names) throws NoSelectedCharacterException {
if (!(account instanceof YggdrasilAccount))
return CharacterSelector.DEFAULT.select(account, names);
YggdrasilAccount yggdrasilAccount = (YggdrasilAccount) account;
for (GameProfile profile : names) {
Image image;
try {
image = AccountHelper.getSkinImmediately(yggdrasilAccount, profile, 4, Settings.INSTANCE.getProxy());
} catch (Exception e) {
image = FXUtils.DEFAULT_ICON;
}
ImageView portraitView = new ImageView();
portraitView.setSmooth(false);
if (image == FXUtils.DEFAULT_ICON)
portraitView.setImage(FXUtils.DEFAULT_ICON);
else {
portraitView.setImage(image);
portraitView.setViewport(AccountHelper.getViewport(4));
}
FXUtils.limitSize(portraitView, 32, 32);
IconedItem accountItem = new IconedItem(portraitView, profile.getName());
accountItem.setOnMouseClicked(e -> {
selectedProfile = profile;
latch.countDown();
});
listBox.add(accountItem);
}
JFXUtilities.runInFX(() -> Controllers.dialog(this));
try {
latch.await();
JFXUtilities.runInFX(Controllers::closeDialog);
if (selectedProfile == null)
throw new NoSelectedCharacterException(account);
return selectedProfile;
} catch (InterruptedException ignore) {
throw new NoSelectedCharacterException(account);
}
}
}
}
@@ -0,0 +1,63 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.effects.JFXDepthManager;
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.AuthlibInjectorServerInfo;
import java.util.function.Consumer;
public final class AuthlibInjectorServerItem extends BorderPane {
private final AuthlibInjectorServerInfo info;
private final Label lblServerName = new Label();
private final Label lblServerIp = new Label();
public AuthlibInjectorServerItem(AuthlibInjectorServerInfo info, Consumer<AuthlibInjectorServerItem> deleteCallback) {
this.info = info;
lblServerName.setStyle("-fx-font-size: 15;");
lblServerIp.setStyle("-fx-font-size: 10;");
VBox center = new VBox();
BorderPane.setAlignment(center, Pos.CENTER);
center.getChildren().addAll(lblServerName, lblServerIp);
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("black", 15, 15));
setRight(right);
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
lblServerName.setText(info.getServerName());
lblServerIp.setText(info.getServerIp());
}
public AuthlibInjectorServerInfo getInfo() {
return info;
}
}
@@ -0,0 +1,154 @@
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServerInfo;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.NetworkUtils;
import java.util.Collection;
import java.util.Objects;
import java.util.stream.Collectors;
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("account.injector.server"));
@FXML private ScrollPane scrollPane;
@FXML private StackPane addServerContainer;
@FXML private Label lblServerIp;
@FXML private Label lblServerName;
@FXML private Label lblCreationWarning;
@FXML private Label lblServerWarning;
@FXML private VBox listPane;
@FXML private JFXTextField txtServerIp;
@FXML private JFXDialogLayout addServerPane;
@FXML private JFXDialogLayout confirmServerPane;
@FXML private JFXDialog dialog;
@FXML private StackPane contentPane;
@FXML private JFXSpinner spinner;
@FXML private JFXProgressBar progressBar;
@FXML private JFXButton btnAddNext;
private final TransitionHandler transitionHandler;
{
FXUtils.loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
FXUtils.smoothScrolling(scrollPane);
transitionHandler = new TransitionHandler(addServerContainer);
getChildren().remove(dialog);
dialog.setDialogContainer(this);
txtServerIp.textProperty().addListener((a, b, newValue) ->
btnAddNext.setDisable(!txtServerIp.validate()));
loading();
}
private void removeServer(AuthlibInjectorServerItem item) {
Settings.INSTANCE.removeAuthlibInjectorServerURL(item.getInfo().getServerIp());
loading();
}
private void loading() {
getChildren().remove(contentPane);
spinner.setVisible(true);
Task.ofResult("list", () -> Settings.INSTANCE.getAuthlibInjectorServerURLs().parallelStream()
.map(serverURL -> new AuthlibInjectorServerItem(new AuthlibInjectorServerInfo(serverURL, Accounts.getAuthlibInjectorServerName(serverURL)), this::removeServer))
.collect(Collectors.toList()))
.subscribe(Task.of(Schedulers.javafx(), variables -> {
listPane.getChildren().setAll(variables.<Collection<? extends Node>>get("list"));
loadingCompleted();
}));
}
private void loadingCompleted() {
getChildren().add(contentPane);
spinner.setVisible(false);
if (Settings.INSTANCE.getAuthlibInjectorServerURLs().isEmpty())
onAdd();
}
@FXML
private void onAdd() {
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
txtServerIp.setText("");
addServerPane.setDisable(false);
progressBar.setVisible(false);
dialog.show();
}
@FXML
private void onAddCancel() {
dialog.close();
}
@FXML
private void onAddNext() {
String serverIp = txtServerIp.getText();
progressBar.setVisible(true);
addServerPane.setDisable(true);
Task.ofResult("serverName", () -> Objects.requireNonNull(Accounts.getAuthlibInjectorServerName(serverIp)))
.finalized(Schedulers.javafx(), (variables, isDependentsSucceeded) -> {
progressBar.setVisible(false);
addServerPane.setDisable(false);
if (isDependentsSucceeded) {
lblServerName.setText(variables.get("serverName"));
lblServerIp.setText(txtServerIp.getText());
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverIp).getProtocol()));
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else
lblCreationWarning.setText(variables.<Exception>get("lastException").getLocalizedMessage());
}).start();
}
@FXML
private void onAddPrev() {
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
}
@FXML
private void onAddFinish() {
String ip = txtServerIp.getText();
if (!ip.endsWith("/"))
ip += "/";
Settings.INSTANCE.addAuthlibInjectorServerURL(ip);
loading();
dialog.close();
}
public String getTitle() {
return title.get();
}
@Override
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
}
@@ -0,0 +1,183 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXDialog;
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.Main;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.InputDialogPane;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.JavaVersion;
import java.util.function.Consumer;
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 AuthlibInjectorServersPage serversPage = null;
private static LeftPaneController leftPaneController;
private static Decorator 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 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 Decorator getDecorator() {
return decorator;
}
public static MainPage getMainPage() {
if (mainPage == null)
mainPage = new MainPage();
return mainPage;
}
public static LeftPaneController getLeftPaneController() {
return leftPaneController;
}
public static void initialize(Stage stage) {
Controllers.stage = stage;
decorator = new Decorator(stage, getMainPage(), Main.TITLE, false, true);
decorator.showPage(null);
leftPaneController = new LeftPaneController(decorator.getLeftPane());
Settings.INSTANCE.onProfileLoading();
Task.of(JavaVersion::initialize).start();
decorator.setCustomMaximize(false);
scene = new Scene(decorator, 804, 521);
scene.getStylesheets().setAll(Settings.INSTANCE.getTheme().getStylesheets());
stage.setMinWidth(804);
stage.setMaxWidth(804);
stage.setMinHeight(521);
stage.setMaxHeight(521);
stage.getIcons().add(new Image("/assets/img/icon.png"));
stage.setTitle(Main.TITLE);
}
public static Region getDialogContent() {
return decorator.getDialog().getContent();
}
public static JFXDialog dialog(Region content) {
// TODO: temp fix
decorator.showDialog(new Region());
return decorator.showDialog(content);
}
public static void dialog(String text) {
dialog(text, null);
}
public static void dialog(String text, String title) {
dialog(text, title, MessageBox.INFORMATION_MESSAGE);
}
public static void dialog(String text, String title, int type) {
dialog(text, title, type, null);
}
public static void dialog(String text, String title, int type, Runnable onAccept) {
dialog(new MessageDialogPane(text, title, decorator.getDialog(), type, onAccept));
}
public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
dialog(new MessageDialogPane(text, title, decorator.getDialog(), onAccept, onCancel));
}
public static void inputDialog(String text, Consumer<String> onResult) {
dialog(new InputDialogPane(text, decorator.getDialog(), onResult));
}
public static void taskDialog(TaskExecutor executor, String title, String subtitle, Runnable onCancel) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
pane.setTitle(title);
pane.setSubtitle(subtitle);
pane.setExecutor(executor);
executor.start();
dialog(pane);
}
public static void closeDialog() {
decorator.getDialog().close();
}
public static void navigate(Node node) {
if (decorator.getNowPage() == node)
decorator.showPage(null);
else
decorator.showPage(node);
}
public static void showUpdate() {
getDecorator().showUpdate();
}
public static void shutdown() {
mainPage = null;
settingsPage = null;
versionPage = null;
serversPage = null;
decorator = null;
stage = null;
scene = null;
}
}
@@ -0,0 +1,73 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import javafx.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.Main;
/**
* @author huangyuhui
*/
public class CrashWindow extends Stage {
public CrashWindow(String text) {
Label lblCrash = new Label();
if (Main.UPDATE_CHECKER.isOutOfDate())
lblCrash.setText(Main.i18n("launcher.crash_out_dated"));
else
lblCrash.setText(Main.i18n("launcher.crash"));
lblCrash.setWrapText(true);
TextArea textArea = new TextArea();
textArea.setText(text);
textArea.setEditable(false);
Button btnContact = new Button();
btnContact.setText(Main.i18n("launcher.contact"));
btnContact.setOnMouseClicked(event -> FXUtils.openLink(Main.CONTACT));
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(Main.i18n("message.error"));
setOnCloseRequest(e -> System.exit(1));
}
}
@@ -0,0 +1,631 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialog;
import com.jfoenix.controls.JFXDrawer;
import com.jfoenix.controls.JFXHamburger;
import com.jfoenix.svg.SVGGlyph;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.fxml.FXML;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Rectangle2D;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.ui.animation.AnimationProducer;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogWizardDisplayer;
import org.jackhuang.hmcl.ui.wizard.*;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.ConcurrentLinkedQueue;
public final class Decorator extends StackPane implements TaskExecutorDialogWizardDisplayer {
private static final SVGGlyph minus = Lang.apply(new SVGGlyph(0, "MINUS", "M804.571 420.571v109.714q0 22.857-16 38.857t-38.857 16h-694.857q-22.857 0-38.857-16t-16-38.857v-109.714q0-22.857 16-38.857t38.857-16h694.857q22.857 0 38.857 16t16 38.857z", Color.WHITE),
glyph -> { glyph.setSize(12, 2); glyph.setTranslateY(4); });
private static final SVGGlyph resizeMax = Lang.apply(new SVGGlyph(0, "RESIZE_MAX", "M726 810v-596h-428v596h428zM726 44q34 0 59 25t25 59v768q0 34-25 60t-59 26h-428q-34 0-59-26t-25-60v-768q0-34 25-60t59-26z", Color.WHITE),
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
private static final SVGGlyph resizeMin = Lang.apply(new SVGGlyph(0, "RESIZE_MIN", "M80.842 943.158v-377.264h565.894v377.264h-565.894zM0 404.21v619.79h727.578v-619.79h-727.578zM377.264 161.684h565.894v377.264h-134.736v80.842h215.578v-619.79h-727.578v323.37h80.842v-161.686z", Color.WHITE),
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
private static final SVGGlyph close = Lang.apply(new SVGGlyph(0, "CLOSE", "M810 274l-238 238 238 238-60 60-238-238-238 238-60-60 238-238-238-238 60-60 238 238 238-238z", Color.WHITE),
glyph -> { glyph.setPrefSize(12, 12); glyph.setSize(12, 12); });
private final ObjectProperty<Runnable> onCloseButtonAction = new SimpleObjectProperty<>(Main::stopApplication);
private final BooleanProperty customMaximize = new SimpleBooleanProperty(false);
private final Stage primaryStage;
private final Node mainPage;
private final boolean max, min;
private final WizardController wizardController = new WizardController(this);
private final Queue<Object> cancelQueue = new ConcurrentLinkedQueue<>();
private double xOffset, yOffset, newX, newY, initX, initY;
private boolean allowMove, isDragging, dialogShown, maximized;
private BoundingBox originalBox, maximizedBox;
private final TransitionHandler animationHandler;
@FXML
private StackPane contentPlaceHolder;
@FXML
private StackPane drawerWrapper;
@FXML
private BorderPane titleContainer;
@FXML
private BorderPane leftRootPane;
@FXML
private HBox buttonsContainer;
@FXML
private JFXButton backNavButton;
@FXML
private JFXButton refreshNavButton;
@FXML
private JFXButton closeNavButton;
@FXML
private JFXButton refreshMenuButton;
@FXML
private Label titleLabel;
@FXML
private Label lblTitle;
@FXML
private AdvancedListBox leftPane;
@FXML
private JFXDrawer drawer;
@FXML
private StackPane titleBurgerContainer;
@FXML
private JFXHamburger titleBurger;
@FXML
private JFXDialog dialog;
@FXML
private JFXButton btnMin;
@FXML
private JFXButton btnMax;
@FXML
private JFXButton btnClose;
@FXML
private HBox updatePane;
@FXML
private HBox navLeft;
public Decorator(Stage primaryStage, Node mainPage, String title) {
this(primaryStage, mainPage, title, true, true);
}
public Decorator(Stage primaryStage, Node mainPage, String title, boolean max, boolean min) {
this.primaryStage = primaryStage;
this.mainPage = mainPage;
this.max = max;
this.min = min;
FXUtils.loadFXML(this, "/assets/fxml/decorator.fxml");
updatePane.setCursor(Cursor.HAND);
updatePane.setOnMouseClicked(event -> Main.UPDATE_CHECKER.checkOutdate());
primaryStage.initStyle(StageStyle.UNDECORATED);
btnClose.setGraphic(close);
btnMin.setGraphic(minus);
btnMax.setGraphic(resizeMax);
lblTitle.setText(title);
buttonsContainer.setBackground(new Background(new BackgroundFill(Color.BLACK, CornerRadii.EMPTY, Insets.EMPTY)));
titleContainer.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
if (event.getClickCount() == 2)
btnMax.fire();
});
dialog.setDialogContainer(drawerWrapper);
dialog.setOnDialogClosed(e -> dialogShown = false);
dialog.setOnDialogOpened(e -> dialogShown = true);
if (!min) buttonsContainer.getChildren().remove(btnMin);
if (!max) buttonsContainer.getChildren().remove(btnMax);
//JFXDepthManager.setDepth(titleContainer, 1);
titleContainer.addEventHandler(MouseEvent.MOUSE_ENTERED, e -> allowMove = true);
titleContainer.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
if (!isDragging) allowMove = false;
});
Rectangle rectangle = new Rectangle(0, 0, 0, 0);
rectangle.widthProperty().bind(titleContainer.widthProperty());
rectangle.heightProperty().bind(Bindings.createDoubleBinding(() -> titleContainer.getHeight() + 100, titleContainer.heightProperty()));
titleContainer.setClip(rectangle);
animationHandler = new TransitionHandler(contentPlaceHolder);
loadBackground();
}
private void loadBackground() {
Image background;
if (Settings.INSTANCE.getBackgroundImageType() == EnumBackgroundImage.DEFAULT)
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), "");
else
background = searchBackgroundImage(new Image("/assets/img/background.jpg"), Settings.INSTANCE.getBackgroundImage());
drawerWrapper.setBackground(new Background(new BackgroundImage(background, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(800, 480, false, false, true, true))));
}
private static Image searchBackgroundImage(Image def, String customPath) {
Random random = new Random();
boolean loaded = false;
Image background = def;
// custom path
if (StringUtils.isNotBlank(customPath)) {
try {
background = new Image("file:" + customPath);
loaded = true;
} catch (IllegalArgumentException ignore) {
}
}
// images in ./bg
if (!loaded) {
File backgroundImageFile = new File("bg");
if (backgroundImageFile.isDirectory()) {
File[] backgroundPath = backgroundImageFile.listFiles(file -> StringUtils.containsOne(FileUtils.getExtension(file), "png", "jpg"));
if (backgroundPath != null && backgroundPath.length > 0) {
int index = random.nextInt(backgroundPath.length);
try {
background = new Image("file:" + backgroundPath[index].getAbsolutePath());
loaded = true;
} catch (IllegalArgumentException ignore) {
}
}
}
}
// background.png
if (!loaded) {
File backgroundImageFile = new File("background.png");
if (backgroundImageFile.exists()) {
try {
background = new Image("file:" + backgroundImageFile.getAbsolutePath());
loaded = true;
} catch (IllegalArgumentException ignore) {
}
}
}
// background.jpg
if (!loaded) {
File backgroundImageFile = new File("background.jpg");
if (backgroundImageFile.exists()) {
try {
background = new Image("file:" + backgroundImageFile.getAbsolutePath());
loaded = true;
} catch (IllegalArgumentException ignore) {
}
}
}
return background;
}
@FXML
private void onMouseMoved(MouseEvent mouseEvent) {
if (!primaryStage.isMaximized() && !primaryStage.isFullScreen() && !maximized) {
if (!primaryStage.isResizable())
updateInitMouseValues(mouseEvent);
else {
double x = mouseEvent.getX(), y = mouseEvent.getY();
Bounds boundsInParent = getBoundsInParent();
if (getBorder() != null && getBorder().getStrokes().size() > 0) {
double borderWidth = this.contentPlaceHolder.snappedLeftInset();
if (this.isRightEdge(x, y, boundsInParent)) {
if (y < borderWidth) {
setCursor(Cursor.NE_RESIZE);
} else if (y > this.getHeight() - borderWidth) {
setCursor(Cursor.SE_RESIZE);
} else {
setCursor(Cursor.E_RESIZE);
}
} else if (this.isLeftEdge(x, y, boundsInParent)) {
if (y < borderWidth) {
setCursor(Cursor.NW_RESIZE);
} else if (y > this.getHeight() - borderWidth) {
setCursor(Cursor.SW_RESIZE);
} else {
setCursor(Cursor.W_RESIZE);
}
} else if (this.isTopEdge(x, y, boundsInParent)) {
setCursor(Cursor.N_RESIZE);
} else if (this.isBottomEdge(x, y, boundsInParent)) {
setCursor(Cursor.S_RESIZE);
} else {
setCursor(Cursor.DEFAULT);
}
this.updateInitMouseValues(mouseEvent);
}
}
} else {
setCursor(Cursor.DEFAULT);
}
}
@FXML
private void onMouseReleased() {
isDragging = false;
}
@FXML
private void onMouseDragged(MouseEvent mouseEvent) {
this.isDragging = true;
if (mouseEvent.isPrimaryButtonDown() && (this.xOffset != -1.0 || this.yOffset != -1.0)) {
if (!this.primaryStage.isFullScreen() && !mouseEvent.isStillSincePress() && !this.primaryStage.isMaximized() && !this.maximized) {
this.newX = mouseEvent.getScreenX();
this.newY = mouseEvent.getScreenY();
double deltaX = this.newX - this.initX;
double deltaY = this.newY - this.initY;
Cursor cursor = this.getCursor();
if (Cursor.E_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
mouseEvent.consume();
} else if (Cursor.NE_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
mouseEvent.consume();
} else if (Cursor.SE_RESIZE == cursor) {
this.setStageWidth(this.primaryStage.getWidth() + deltaX);
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.S_RESIZE == cursor) {
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.W_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
mouseEvent.consume();
} else if (Cursor.SW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
this.setStageHeight(this.primaryStage.getHeight() + deltaY);
mouseEvent.consume();
} else if (Cursor.NW_RESIZE == cursor) {
if (this.setStageWidth(this.primaryStage.getWidth() - deltaX)) {
this.primaryStage.setX(this.primaryStage.getX() + deltaX);
}
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
mouseEvent.consume();
} else if (Cursor.N_RESIZE == cursor) {
if (this.setStageHeight(this.primaryStage.getHeight() - deltaY)) {
this.primaryStage.setY(this.primaryStage.getY() + deltaY);
}
mouseEvent.consume();
} else if (this.allowMove) {
this.primaryStage.setX(mouseEvent.getScreenX() - this.xOffset);
this.primaryStage.setY(mouseEvent.getScreenY() - this.yOffset);
mouseEvent.consume();
}
}
}
}
@FXML
private void onMin() {
primaryStage.setIconified(true);
}
@FXML
private void onMax() {
if (!max) return;
if (!this.isCustomMaximize()) {
this.primaryStage.setMaximized(!this.primaryStage.isMaximized());
this.maximized = this.primaryStage.isMaximized();
if (this.primaryStage.isMaximized()) {
this.btnMax.setGraphic(resizeMin);
this.btnMax.setTooltip(new Tooltip("Restore Down"));
} else {
this.btnMax.setGraphic(resizeMax);
this.btnMax.setTooltip(new Tooltip("Maximize"));
}
} else {
if (!this.maximized) {
this.originalBox = new BoundingBox(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight());
Screen screen = Screen.getScreensForRectangle(primaryStage.getX(), primaryStage.getY(), primaryStage.getWidth(), primaryStage.getHeight()).get(0);
Rectangle2D bounds = screen.getVisualBounds();
this.maximizedBox = new BoundingBox(bounds.getMinX(), bounds.getMinY(), bounds.getWidth(), bounds.getHeight());
primaryStage.setX(this.maximizedBox.getMinX());
primaryStage.setY(this.maximizedBox.getMinY());
primaryStage.setWidth(this.maximizedBox.getWidth());
primaryStage.setHeight(this.maximizedBox.getHeight());
this.btnMax.setGraphic(resizeMin);
this.btnMax.setTooltip(new Tooltip("Restore Down"));
} else {
primaryStage.setX(this.originalBox.getMinX());
primaryStage.setY(this.originalBox.getMinY());
primaryStage.setWidth(this.originalBox.getWidth());
primaryStage.setHeight(this.originalBox.getHeight());
this.originalBox = null;
this.btnMax.setGraphic(resizeMax);
this.btnMax.setTooltip(new Tooltip("Maximize"));
}
this.maximized = !this.maximized;
}
}
@FXML
private void onClose() {
onCloseButtonAction.get().run();
}
private void updateInitMouseValues(MouseEvent mouseEvent) {
initX = mouseEvent.getScreenX();
initY = mouseEvent.getScreenY();
xOffset = mouseEvent.getSceneX();
yOffset = mouseEvent.getSceneY();
}
private boolean isRightEdge(double x, double y, Bounds boundsInParent) {
return x < getWidth() && x > getWidth() - contentPlaceHolder.snappedLeftInset();
}
private boolean isTopEdge(double x, double y, Bounds boundsInParent) {
return y >= 0 && y < contentPlaceHolder.snappedLeftInset();
}
private boolean isBottomEdge(double x, double y, Bounds boundsInParent) {
return y < getHeight() && y > getHeight() - contentPlaceHolder.snappedLeftInset();
}
private boolean isLeftEdge(double x, double y, Bounds boundsInParent) {
return x >= 0 && x < contentPlaceHolder.snappedLeftInset();
}
private boolean setStageWidth(double width) {
if (width >= primaryStage.getMinWidth() && width >= titleContainer.getMinWidth()) {
primaryStage.setWidth(width);
initX = newX;
return true;
} else {
if (width >= primaryStage.getMinWidth() && width <= titleContainer.getMinWidth())
primaryStage.setWidth(titleContainer.getMinWidth());
return false;
}
}
private boolean setStageHeight(double height) {
if (height >= primaryStage.getMinHeight() && height >= titleContainer.getHeight()) {
primaryStage.setHeight(height);
initY = newY;
return true;
} else {
if (height >= primaryStage.getMinHeight() && height <= titleContainer.getHeight())
primaryStage.setHeight(titleContainer.getHeight());
return false;
}
}
public void setMaximized(boolean maximized) {
if (this.maximized != maximized) {
Platform.runLater(btnMax::fire);
}
}
public void showUpdate() {
updatePane.setVisible(true);
}
private void showCloseNavButton() {
navLeft.getChildren().add(closeNavButton);
}
private void hideCloseNavButton() {
navLeft.getChildren().remove(closeNavButton);
}
private void setContent(Node content, AnimationProducer animation) {
animationHandler.setContent(content, animation);
if (content instanceof Region) {
((Region) content).setMinSize(0, 0);
FXUtils.setOverflowHidden((Region) content, true);
}
if (content instanceof Refreshable)
refreshNavButton.setVisible(true);
if (content != mainPage)
backNavButton.setVisible(true);
String prefix = category == null ? "" : category + " - ";
titleLabel.textProperty().unbind();
if (content instanceof WizardPage)
titleLabel.setText(prefix + ((WizardPage) content).getTitle());
if (content instanceof DecoratorPage)
titleLabel.textProperty().bind(((DecoratorPage) content).titleProperty());
}
private String category;
private Node nowPage;
public Node getNowPage() {
return nowPage;
}
public void showPage(Node content) {
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
if (content != null)
contentPlaceHolder.getStyleClass().add("gray-background");
Node c = content == null ? mainPage : content;
onEnd();
if (nowPage instanceof DecoratorPage)
((DecoratorPage) nowPage).onClose();
nowPage = content;
setContent(c, ContainerAnimations.FADE.getAnimationProducer());
if (c instanceof Region) {
// Let root pane fix window size.
StackPane parent = (StackPane) c.getParent();
((Region) c).prefWidthProperty().bind(parent.widthProperty());
((Region) c).prefHeightProperty().bind(parent.heightProperty());
}
}
public JFXDialog showDialog(Region content) {
dialog.setContent(content);
if (!dialogShown)
dialog.show();
return dialog;
}
public void startWizard(WizardProvider wizardProvider) {
startWizard(wizardProvider, null);
}
public void startWizard(WizardProvider wizardProvider, String category) {
this.category = category;
wizardController.setProvider(wizardProvider);
wizardController.onStart();
}
@Override
public void onStart() {
backNavButton.setVisible(true);
backNavButton.setDisable(false);
showCloseNavButton();
refreshNavButton.setVisible(false);
}
@Override
public void onEnd() {
backNavButton.setVisible(false);
hideCloseNavButton();
refreshNavButton.setVisible(false);
}
@Override
public void navigateTo(Node page, Navigation.NavigationDirection nav) {
contentPlaceHolder.getStyleClass().removeAll("gray-background", "white-background");
contentPlaceHolder.getStyleClass().add("white-background");
setContent(page, nav.getAnimation().getAnimationProducer());
}
@FXML
private void onRefresh() {
((Refreshable) contentPlaceHolder.getChildren().get(0)).refresh();
}
@FXML
private void onCloseNav() {
wizardController.onCancel();
showPage(null);
}
@FXML
private void onBack() {
if (wizardController.canPrev())
wizardController.onPrev(true);
else
onCloseNav();
}
@Override
public Queue<Object> getCancelQueue() {
return cancelQueue;
}
public Runnable getOnCloseButtonAction() {
return onCloseButtonAction.get();
}
public ObjectProperty<Runnable> onCloseButtonActionProperty() {
return onCloseButtonAction;
}
public void setOnCloseButtonAction(Runnable onCloseButtonAction) {
this.onCloseButtonAction.set(onCloseButtonAction);
}
public boolean isCustomMaximize() {
return customMaximize.get();
}
public BooleanProperty customMaximizeProperty() {
return customMaximize;
}
public void setCustomMaximize(boolean customMaximize) {
this.customMaximize.set(customMaximize);
}
@Override
public WizardController getWizardController() {
return wizardController;
}
public JFXDialog getDialog() {
return dialog;
}
public AdvancedListBox getLeftPane() {
return leftPane;
}
}
@@ -0,0 +1,52 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.task.SilentException;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
public final class DialogController {
public static AuthInfo logIn(Account account) throws Exception {
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();
Controllers.closeDialog();
}, () -> {
latch.countDown();
Controllers.closeDialog();
});
pane.setDialog(Controllers.dialog(pane));
});
latch.await();
return Optional.ofNullable(res.get()).orElseThrow(SilentException::new);
}
return account.logIn();
}
}
@@ -1,73 +0,0 @@
/*
* 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) {
}
}
@@ -0,0 +1,384 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.adapters.ReflectionHelper;
import com.jfoenix.controls.*;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
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.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.control.ScrollBar;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.Region;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.List;
import java.util.function.Consumer;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.ReflectionHelper.call;
import static org.jackhuang.hmcl.util.ReflectionHelper.construct;
public final class FXUtils {
private FXUtils() {
}
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) {
onChange(value, consumer);
consumer.accept(value.getValue());
}
public static <T> void onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
onWeakChange(value, consumer);
consumer.accept(value.getValue());
}
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) {
Lang.cast(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 void smoothScrolling(ScrollPane scrollPane) {
JFXScrollPane.smoothScrolling(scrollPane);
}
public static void loadFXML(Node node, String absolutePath) {
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), Main.RESOURCE_BUNDLE);
loader.setRoot(node);
loader.setController(node);
Lang.invoke((ExceptionalSupplier<Object, IOException>) loader::load);
}
public static void resetChildren(JFXMasonryPane pane, List<Node> children) {
// Fixes mis-repositioning.
ReflectionHelper.setFieldContent(JFXMasonryPane.class, pane, "oldBoxes", null);
pane.getChildren().setAll(children);
}
public static void installTooltip(Node node, String tooltip) {
installTooltip(node, 0, 5000, 0, new Tooltip(tooltip));
}
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
try {
// Java 8
call(construct(Class.forName("javafx.scene.control.Tooltip$TooltipBehavior"), new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false),
"install", node, tooltip);
} catch (Throwable e) {
try {
// Java 9
call(tooltip, "setShowDelay", new Duration(openDelay));
call(tooltip, "setShowDuration", new Duration(visibleDelay));
call(tooltip, "setHideDelay", new Duration(closeDelay));
} catch (Throwable e2) {
e.addSuppressed(e2);
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip by reflection", 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:
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;
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, SafeIntStringConverter.INSTANCE);
}
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)
*/
@SuppressWarnings("unchecked")
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")
public static void unbindEnum(JFXComboBox<?> comboBox) {
ChangeListener listener = Lang.get(comboBox.getProperties(), "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 final Image DEFAULT_ICON = new Image("/assets/img/icon.png");
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";
}
};
}
@@ -1,85 +0,0 @@
<?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(&quot;{key}&quot;)"/>
</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(&quot;install.version&quot;), C.i18n(&quot;install.release_time&quot;), C.i18n(&quot;install.time&quot;), C.i18n(&quot;install.type&quot;)},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(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnRefreshGameDownloadsActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>
@@ -1,172 +0,0 @@
/*
* 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();
}
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -1,108 +0,0 @@
/*
* 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);
}
}
@@ -0,0 +1,96 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.game.GameVersion;
import org.jackhuang.hmcl.game.Library;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.download.InstallerWizardProvider;
import java.util.LinkedList;
import java.util.Optional;
import java.util.function.Consumer;
public class InstallerController {
private Profile profile;
private String versionId;
private Version version;
@FXML
private ScrollPane scrollPane;
@FXML private VBox contentPane;
private String forge;
private String liteLoader;
private String optiFine;
@FXML
private void initialize() {
FXUtils.smoothScrolling(scrollPane);
}
public void loadVersion(Profile profile, String versionId) {
this.profile = profile;
this.versionId = versionId;
this.version = profile.getRepository().getVersion(versionId).resolve(profile.getRepository());
contentPane.getChildren().clear();
forge = liteLoader = optiFine = null;
for (Library library : version.getLibraries()) {
Consumer<InstallerItem> removeAction = x -> {
LinkedList<Library> newList = new LinkedList<>(version.getLibraries());
newList.remove(library);
new MaintainTask(profile.getRepository(), version.setLibraries(newList))
.then(variables -> new VersionJsonSaveTask(profile.getRepository(), variables.get(MaintainTask.ID)))
.with(profile.getRepository().refreshVersionsAsync())
.with(Task.of(Schedulers.javafx(), () -> loadVersion(this.profile, this.versionId)))
.start();
};
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
contentPane.getChildren().add(new InstallerItem("Forge", library.getVersion(), removeAction));
forge = library.getVersion();
}
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
contentPane.getChildren().add(new InstallerItem("LiteLoader", library.getVersion(), removeAction));
liteLoader = library.getVersion();
}
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
contentPane.getChildren().add(new InstallerItem("OptiFine", library.getVersion(), removeAction));
optiFine = library.getVersion();
}
}
}
@FXML
private void onAdd() {
Optional<String> gameVersion = GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version));
if (!gameVersion.isPresent())
Controllers.dialog("version.cannot_read");
else
Controllers.getDecorator().startWizard(new InstallerWizardProvider(profile, gameVersion.get(), version, forge, liteLoader, optiFine));
}
}
@@ -0,0 +1,54 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.effects.JFXDepthManager;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.Main;
import java.util.function.Consumer;
/**
* @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(Main.i18n("archive.version") + ": " + version);
}
@FXML
private void onDelete() {
deleteCallback.accept(this);
}
}
@@ -1,85 +0,0 @@
<?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(&quot;{key}&quot;)"/>
</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(&quot;install.version&quot;), C.i18n(&quot;install.mcversion&quot;)},&#xa; 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(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnRefreshActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>
@@ -1,210 +0,0 @@
/*
* 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
}
@@ -1,101 +0,0 @@
/*
* 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());
}
}
@@ -1,582 +0,0 @@
<?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(&quot;{key}&quot;)"/>
</Property>
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
<Color id="&#x624b;&#x578b;&#x5149;&#x6807;"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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="418" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="1" attributes="0">
<Component id="lblProxy" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" 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>
<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"/>
</Group>
<EmptySpace min="-2" pref="28" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<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"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;color.blue&quot;),C.i18n(&quot;color.green&quot;),C.i18n(&quot;color.purple&quot;),C.i18n(&quot;color.dark_blue&quot;),C.i18n(&quot;color.orange&quot;),C.i18n(&quot;color.red&quot;)})" 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(&quot;{key}&quot;)"/>
</Property>
</Properties>
</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(&quot;{key}&quot;)"/>
</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="15" 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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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="104" 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(&quot;{key}&quot;)"/>
</Property>
<Property name="cursor" type="java.awt.Cursor" editor="org.netbeans.modules.form.editors2.CursorEditor">
<Color id="&#x9ed8;&#x8ba4;&#x5149;&#x6807;"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Container>
</SubComponents>
</Container>
</SubComponents>
</Form>
@@ -1,655 +0,0 @@
/*
* 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 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());
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();
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
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, 418, Short.MAX_VALUE))
.addGroup(javax.swing.GroupLayout.Alignment.TRAILING, pnlGeneralLayout.createSequentialGroup()
.addComponent(lblProxy)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.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))
.addGroup(pnlGeneralLayout.createSequentialGroup()
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblDownloadSource)
.addComponent(lblCommonPath)
.addComponent(lblLang))
.addGap(28, 28, 28)
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.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))
.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, 15, 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(104, 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
// 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 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
}
@@ -1,257 +0,0 @@
/*
* 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;
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 {
Log4jHandler handler = new Log4jHandler(monitor, os);
handler.addForbiddenToken(obj.getLoginResult().getAccessToken(), "<access token>");
handler.addForbiddenToken(obj.getLoginResult().getSession(), "<session>");
handler.addForbiddenToken(obj.getLoginResult().getUserId(), "<uuid>");
handler.addForbiddenToken("Setting user: " + obj.getLoginResult().getUserName(), "<player>");
handler.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) {
if (!t.isError())
try {
os.write((t.getLine() + C.LINE_SEPARATOR).replace("log4j:Event", "log4j_Event").replace("log4j:Message", "log4j_Message").getBytes());
os.flush();
} catch(IOException e) {
HMCLog.err("", e);
}
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)
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) {
HMCLog.log("Launcher will not exit now.");
MainFrame.INSTANCE.setVisible(true);
} else if (v != LauncherVisibility.KEEP && !LogWindow.INSTANCE.isVisible() && !noExitThisTime) {
HMCLog.log("Launcher will exit now.");
System.exit(0);
} else {
HMCLog.log("Launcher will not exit now.");
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();
};
}
@@ -0,0 +1,168 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.concurrency.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.ProfileChangedEvent;
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.game.AccountHelper;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.ClassTitle;
import org.jackhuang.hmcl.ui.construct.IconedItem;
import org.jackhuang.hmcl.ui.construct.RipplerContainer;
import org.jackhuang.hmcl.util.Lang;
import java.io.File;
import java.util.LinkedList;
import java.util.Objects;
public final class LeftPaneController {
private final AdvancedListBox leftPane;
private final VBox profilePane = new VBox();
private final VersionListItem accountItem = new VersionListItem("", "");
public LeftPaneController(AdvancedListBox leftPane) {
this.leftPane = leftPane;
leftPane.startCategory(Main.i18n("account").toUpperCase())
.add(Lang.apply(new RipplerContainer(accountItem), rippler -> {
rippler.setOnMouseClicked(e -> Controllers.navigate(new AccountsPage()));
accountItem.setOnSettingsButtonClicked(() -> Controllers.navigate(new AccountsPage()));
}))
.startCategory(Main.i18n("launcher").toUpperCase())
.add(Lang.apply(new IconedItem(SVG.gear("black", 20, 20), Main.i18n("settings.launcher")), iconedItem -> {
iconedItem.prefWidthProperty().bind(leftPane.widthProperty());
iconedItem.setOnMouseClicked(e -> Controllers.navigate(Controllers.getSettingsPage()));
}))
.add(new ClassTitle(Lang.apply(new BorderPane(), borderPane -> {
borderPane.setLeft(Lang.apply(new VBox(), vBox -> vBox.getChildren().setAll(new Text(Main.i18n("profile.title").toUpperCase()))));
JFXButton addProfileButton = new JFXButton();
addProfileButton.setGraphic(SVG.plus("black", 10, 10));
addProfileButton.getStyleClass().add("toggle-icon-tiny");
addProfileButton.setOnMouseClicked(e ->
Controllers.getDecorator().showPage(new ProfilePage(null)));
borderPane.setRight(addProfileButton);
})))
.add(profilePane);
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(this::onRefreshedVersions);
FXUtils.onChangeAndOperate(Settings.INSTANCE.selectedAccountProperty(), it -> {
if (it == null) {
accountItem.setVersionName(Main.i18n("account.missing"));
accountItem.setGameVersion(Main.i18n("message.unknown"));
} else {
accountItem.setVersionName(it.getCharacter());
accountItem.setGameVersion(AccountsPage.accountType(it));
}
if (it instanceof YggdrasilAccount) {
Image image = AccountHelper.getSkin((YggdrasilAccount) it, 4);
accountItem.setImage(image, AccountHelper.getViewport(4));
} else
accountItem.setImage(AccountHelper.getDefaultSkin(it, 4), AccountHelper.getViewport(4));
});
}
private void onProfileChanged(ProfileChangedEvent event) {
Profile profile = event.getProfile();
Platform.runLater(() -> {
for (Node node : profilePane.getChildren()) {
if (node instanceof RipplerContainer && node.getProperties().get("profile") instanceof String) {
boolean current = Objects.equals(node.getProperties().get("profile"), profile.getName());
((RipplerContainer) node).setSelected(current);
((VersionListItem) ((RipplerContainer) node).getContainer()).setGameVersion(current ? Main.i18n("profile.selected") : "");
}
}
});
}
private void onProfilesLoading() {
LinkedList<RipplerContainer> list = new LinkedList<>();
for (Profile profile : Settings.INSTANCE.getProfiles()) {
VersionListItem item = new VersionListItem(Profiles.getProfileDisplayName(profile));
RipplerContainer ripplerContainer = new RipplerContainer(item);
item.setOnSettingsButtonClicked(() -> Controllers.getDecorator().showPage(new ProfilePage(profile)));
ripplerContainer.setOnMouseClicked(e -> Settings.INSTANCE.setSelectedProfile(profile));
ripplerContainer.getProperties().put("profile", profile.getName());
ripplerContainer.maxWidthProperty().bind(leftPane.widthProperty());
list.add(ripplerContainer);
}
Platform.runLater(() -> profilePane.getChildren().setAll(list));
}
private boolean checkedModpack = false;
private void onRefreshedVersions(RefreshedVersionsEvent event) {
JFXUtilities.runInFX(() -> {
boolean flag = true;
HMCLGameRepository repository = (HMCLGameRepository) event.getSource();
if (!checkedModpack) {
checkedModpack = true;
if (repository.getVersionCount() == 0) {
File modpackFile = new File("modpack.zip").getAbsoluteFile();
if (modpackFile.exists()) {
try {
Modpack modpack = ModpackHelper.readModpackManifest(modpackFile);
Controllers.taskDialog(ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.with(Task.of(Schedulers.javafx(), () -> {
Controllers.closeDialog();
checkAccount();
})).executor(),
Main.i18n("modpack.installing"), "", null);
flag = false;
} catch (UnsupportedModpackException ignore) {
}
}
}
}
if (flag)
checkAccount();
});
}
private void checkAccount() {
if (Settings.INSTANCE.getAccounts().isEmpty())
Controllers.navigate(new AccountsPage());
}
}
@@ -1,245 +0,0 @@
<?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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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="&lt;String&gt;"/>
</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(&quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
</SubComponents>
</Form>
@@ -1,6 +1,6 @@
/*
* Hello Minecraft!.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,409 +17,227 @@
*/
package org.jackhuang.hmcl.ui;
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 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.Main;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.game.LauncherHelper;
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;
import org.jackhuang.hmcl.util.IOUtils;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Log4jLevel;
import org.jackhuang.hmcl.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.concurrent.CountDownLatch;
/**
*
* @author huangyuhui
*/
public class LogWindow extends javax.swing.JFrame {
public final class LogWindow extends Stage {
public static LogWindowOutputStream outputStream;
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<>();
NonFunction<Boolean> listener;
/**
* Creates new form LogWindow
*/
public LogWindow() {
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()));
});
setScene(new Scene(impl, 800, 480));
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets());
setTitle(Main.i18n("logwindow.title"));
getIcons().add(new Image("/assets/img/icon.png"));
}
public static final LogWindow INSTANCE = new LogWindow();
public LogWindow(String text) {
this();
/**
* 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() {
onDone.register(() -> logLine(text, Log4jLevel.INFO));
}
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 ReadOnlyIntegerProperty fatalProperty() {
return fatal.getReadOnlyProperty();
}
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 int getFatal() {
return fatal.get();
}
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 ReadOnlyIntegerProperty errorProperty() {
return error.getReadOnlyProperty();
}
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 int getError() {
return error.get();
}
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 ReadOnlyIntegerProperty warnProperty() {
return warn.getReadOnlyProperty();
}
lblCrash.setText(C.i18n("ui.label.crashing")); // NOI18N
public int getWarn() {
return warn.get();
}
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 ReadOnlyIntegerProperty infoProperty() {
return info.getReadOnlyProperty();
}
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 int getInfo() {
return info.get();
}
txtLog.setEditable(false);
pnlLog.setViewportView(txtLog);
public ReadOnlyIntegerProperty debugProperty() {
return debug.getReadOnlyProperty();
}
lblShowLines.setText(C.i18n("logwindow.show_lines")); // NOI18N
lblShowLines.setToolTipText("");
public int getDebug() {
return debug.get();
}
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);
}
});
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();");
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.setClipborad(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) {
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;
}
}//GEN-LAST:event_txtSearchActionPerformed
String s = "";
int pos = 0, lastX = 0, lastLen = 0;
void terminateGames() {
ProcessMonitor.stopAll();
}
int removedLength = 0;
Deque<Integer> offsets = new LinkedList<>();
int fatals = 0, errors = 0, warns = 0, infos = 0, debugs = 0;
public void waitForLoaded() throws InterruptedException {
latch.await();
}
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;
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}", Settings.INSTANCE.getFont().getSize() + "px \"" + Settings.INSTANCE.getFont().getFamily() + "\""));
engine.getLoadWorker().stateProperty().addListener((a, b, newValue) -> {
if (newValue == Worker.State.SUCCEEDED) {
document = engine.getDocument();
body = document.getElementsByTagName("body").item(0);
engine.executeScript("limitedLogs=" + Settings.INSTANCE.getLogLines());
latch.countDown();
onDone.fireEvent(new Event(LogWindow.this));
}
} 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);
}
});
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) {
boolean flag = false;
for (String i : cboLines.getItems())
if (Integer.toString(Settings.INSTANCE.getLogLines()).equals(i)) {
cboLines.getSelectionModel().select(i);
flag = true;
}
}
});
}
public void setExit(NonFunction<Boolean> exit) {
this.listener = exit;
}
cboLines.getSelectionModel().selectedItemProperty().addListener((a, b, newValue) -> {
Settings.INSTANCE.setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
engine.executeScript("limitedLogs=" + Settings.INSTANCE.getLogLines());
});
public void clean() {
txtLog.setText("");
}
if (!flag)
cboLines.getSelectionModel().select(0);
@Override
public void setVisible(boolean b) {
txtLog.setFont(Settings.getInstance().getConsoleFont());
lblCrash.setVisible(false);
btnContact.setVisible(false);
super.setVisible(b);
}
btnFatals.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(fatal.get()) + " fatals", fatal));
btnErrors.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(error.get()) + " errors", error));
btnWarns.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(warn.get()) + " warns", warn));
btnInfos.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(info.get()) + " infos", info));
btnDebugs.textProperty().bind(Bindings.createStringBinding(() -> Integer.toString(debug.get()) + " debugs", debug));
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"));
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());
}
super.setVisible(true);
}
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 + "])");
}
// 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
@FXML
private void onTerminateGame() {
LauncherHelper.stopManagedProcesses();
}
@FXML
private void onClear() {
engine.executeScript("clear()");
}
}
}
@@ -1,83 +0,0 @@
/*
* 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);
});
}
}
@@ -1,506 +0,0 @@
/*
* 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) {
if (!enableShadow)
paintImpl(g);
else {
int off = enableShadow ? 16 : 0;
int width = this.getWidth();
int height = this.getHeight();
int contentWidth = width - off - off;
int contentHeight = height - off - off;
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();
}
@@ -0,0 +1,236 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXMasonryPane;
import com.jfoenix.controls.JFXPopup;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.image.Image;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.StackPane;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.ProfileChangedEvent;
import org.jackhuang.hmcl.event.ProfileLoadingEvent;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.game.*;
import org.jackhuang.hmcl.mod.MismatchedModpackTypeException;
import org.jackhuang.hmcl.mod.UnsupportedModpackException;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.construct.MessageBox;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.ui.download.DownloadWizardProvider;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.OperatingSystem;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public final class MainPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("main_page"));
private Profile profile;
private String rightClickedVersion;
@FXML
private JFXButton btnRefresh;
@FXML
private JFXButton btnAdd;
@FXML
private JFXMasonryPane masonryPane;
@FXML
private JFXListView versionList;
private final JFXPopup versionPopup;
{
FXUtils.loadFXML(this, "/assets/fxml/main.fxml");
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(() -> Platform.runLater(this::loadVersions));
EventBus.EVENT_BUS.channel(ProfileLoadingEvent.class).register(this::onProfilesLoading);
EventBus.EVENT_BUS.channel(ProfileChangedEvent.class).register(this::onProfileChanged);
versionPopup = new JFXPopup(versionList);
getChildren().remove(versionList);
btnAdd.setOnMouseClicked(e -> Controllers.getDecorator().startWizard(new DownloadWizardProvider(), Main.i18n("install")));
FXUtils.installTooltip(btnAdd, Main.i18n("install"));
btnRefresh.setOnMouseClicked(e -> Settings.INSTANCE.getSelectedProfile().getRepository().refreshVersionsAsync().start());
FXUtils.installTooltip(btnRefresh, Main.i18n("button.refresh"));
}
private Node buildNode(Profile profile, String version, String game) {
VersionItem item = new VersionItem();
item.setUpdate(profile.getRepository().isModpack(version));
item.setGameVersion(game);
item.setVersionName(version);
StringBuilder libraries = new StringBuilder();
for (Library library : profile.getRepository().getVersion(version).getLibraries()) {
if (library.getGroupId().equalsIgnoreCase("net.minecraftforge") && library.getArtifactId().equalsIgnoreCase("forge")) {
libraries.append(Main.i18n("install.installer.forge")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)forge", "").replace(game, "").trim(), "-"), "-")).append("\n");
}
if (library.getGroupId().equalsIgnoreCase("com.mumfrey") && library.getArtifactId().equalsIgnoreCase("liteloader")) {
libraries.append(Main.i18n("install.installer.liteloader")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)liteloader", "").replace(game, "").trim(), "-"), "-")).append("\n");
}
if (library.getGroupId().equalsIgnoreCase("net.optifine") && library.getArtifactId().equalsIgnoreCase("optifine")) {
libraries.append(Main.i18n("install.installer.optifine")).append(": ").append(StringUtils.removeSuffix(StringUtils.removePrefix(library.getVersion().replaceAll("(?i)optifine", "").replace(game, "").trim(), "-"), "-")).append("\n");
}
}
item.setLibraries(libraries.toString());
item.setOnLaunchButtonClicked(e -> {
if (Settings.INSTANCE.getSelectedAccount() == null)
Controllers.dialog(Main.i18n("login.empty_username"));
else
LauncherHelper.INSTANCE.launch(version, null);
});
item.setOnScriptButtonClicked(e -> {
if (Settings.INSTANCE.getSelectedAccount() == null)
Controllers.dialog(Main.i18n("login.empty_username"));
else {
FileChooser chooser = new FileChooser();
chooser.setInitialDirectory(profile.getRepository().getRunDirectory(version));
chooser.setTitle(Main.i18n("version.launch_script.save"));
chooser.getExtensionFilters().add(OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS
? new FileChooser.ExtensionFilter(Main.i18n("extension.bat"), "*.bat")
: new FileChooser.ExtensionFilter(Main.i18n("extension.sh"), "*.sh"));
File file = chooser.showSaveDialog(Controllers.getStage());
if (file != null)
LauncherHelper.INSTANCE.launch(version, file);
}
});
item.setOnSettingsButtonClicked(e -> {
Controllers.getDecorator().showPage(Controllers.getVersionPage());
Controllers.getVersionPage().load(version, profile);
});
item.setOnUpdateButtonClicked(event -> {
FileChooser chooser = new FileChooser();
chooser.setTitle(Main.i18n("modpack.choose"));
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("modpack"), "*.zip"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(null);
try {
TaskExecutor executor = ModpackHelper.getUpdateTask(profile, selectedFile, version, ModpackHelper.readModpackConfiguration(profile.getRepository().getModpackConfiguration(version)))
.then(Task.of(Schedulers.javafx(), Controllers::closeDialog)).executor();
pane.setExecutor(executor);
pane.setTitle(Main.i18n("modpack.update"));
executor.start();
Controllers.dialog(pane);
} catch (UnsupportedModpackException e) {
Controllers.dialog(Main.i18n("modpack.unsupported"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (MismatchedModpackTypeException e) {
Controllers.dialog(Main.i18n("modpack.mismatched_type"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE);
} catch (IOException e) {
Controllers.dialog(Main.i18n("modpack.invalid"), Main.i18n("message.error"), MessageBox.ERROR_MESSAGE);
}
}
});
item.setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.SECONDARY) {
rightClickedVersion = version;
versionList.getSelectionModel().select(-1);
versionPopup.show(item, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.LEFT, event.getX(), event.getY());
} else if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
if (Settings.INSTANCE.getSelectedAccount() == null)
Controllers.dialog(Main.i18n("login.empty_username"));
else
LauncherHelper.INSTANCE.launch(version, null);
}
});
File iconFile = profile.getRepository().getVersionIcon(version);
if (iconFile.exists())
item.setImage(new Image("file:" + iconFile.getAbsolutePath()));
return item;
}
public void onProfilesLoading() {
// TODO: Profiles
}
public void onProfileChanged(ProfileChangedEvent event) {
Platform.runLater(() -> loadVersions(event.getProfile()));
}
private void loadVersions() {
loadVersions(Settings.INSTANCE.getSelectedProfile());
}
private void loadVersions(Profile profile) {
this.profile = profile;
List<Node> children = new LinkedList<>();
List<Version> versions = new LinkedList<>(profile.getRepository().getVersions());
for (Version version : versions) {
children.add(buildNode(profile, version.getId(), GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version.getId())).orElse("Unknown")));
}
FXUtils.resetChildren(masonryPane, children);
}
@FXML
private void onVersionManagement() {
versionPopup.hide();
switch (versionList.getSelectionModel().getSelectedIndex()) {
case 0:
VersionPage.renameVersion(profile, rightClickedVersion);
break;
case 1:
VersionPage.deleteVersion(profile, rightClickedVersion);
break;
case 2:
VersionPage.exportVersion(profile, rightClickedVersion);
break;
case 3:
FXUtils.openFolder(profile.getRepository().getRunDirectory(rightClickedVersion));
break;
default:
throw new Error();
}
}
public String getTitle() {
return title.get();
}
@Override
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
}
@@ -1,304 +0,0 @@
<?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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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>
@@ -1,553 +0,0 @@
/*
* 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,175 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXSpinner;
import com.jfoenix.controls.JFXTabPane;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.mod.ModInfo;
import org.jackhuang.hmcl.mod.ModManager;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.FileUtils;
import org.jackhuang.hmcl.util.Logging;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Stream;
public final class ModController {
@FXML
private ScrollPane scrollPane;
@FXML private StackPane rootPane;
@FXML private VBox modPane;
@FXML private StackPane contentPane;
@FXML private JFXSpinner spinner;
private JFXTabPane parentTab;
private ModManager modManager;
private String versionId;
@FXML
private void initialize() {
FXUtils.smoothScrolling(scrollPane);
rootPane.setOnDragOver(event -> {
if (event.getGestureSource() != rootPane && event.getDragboard().hasFiles())
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
event.consume();
});
rootPane.setOnDragDropped(event -> {
List<File> mods = event.getDragboard().getFiles();
Stream<File> stream = null;
if (mods != null)
stream = mods.stream()
.filter(it -> Arrays.asList("jar", "zip", "litemod").contains(FileUtils.getExtension(it)));
if (stream != null && stream.findAny().isPresent()) {
stream.forEach(it -> {
try {
modManager.addMod(versionId, it);
} catch (IOException | IllegalArgumentException e) {
Logging.LOG.log(Level.WARNING, "Unable to parse mod file " + it, e);
}
});
loadMods(modManager, versionId);
event.setDropCompleted(true);
}
event.consume();
});
}
public void loadMods(ModManager modManager, String versionId) {
this.modManager = modManager;
this.versionId = versionId;
Task.of(variables -> {
synchronized (ModController.this) {
Platform.runLater(() -> {
rootPane.getChildren().remove(contentPane);
spinner.setVisible(true);
});
modManager.refreshMods(versionId);
// Surprisingly, if there are a great number of mods, this processing will cause a long UI pause,
// constructing UI elements.
// We must do this asynchronously.
LinkedList<ModItem> list = new LinkedList<>();
for (ModInfo modInfo : modManager.getMods(versionId)) {
ModItem item = new ModItem(modInfo, i -> {
modManager.removeMods(versionId, modInfo);
loadMods(modManager, versionId);
});
modInfo.activeProperty().addListener((a, b, newValue) -> {
if (newValue)
item.getStyleClass().remove("disabled");
else
item.getStyleClass().add("disabled");
});
if (!modInfo.isActive())
item.getStyleClass().add("disabled");
list.add(item);
}
Platform.runLater(() -> {
rootPane.getChildren().add(contentPane);
spinner.setVisible(false);
});
variables.set("list", list);
}
}).finalized(Schedulers.javafx(), variables -> {
FXUtils.onWeakChangeAndOperate(parentTab.getSelectionModel().selectedItemProperty(), newValue -> {
if (newValue != null && newValue.getUserData() == ModController.this)
modPane.getChildren().setAll(variables.<List<ModItem>>get("list"));
});
}, null).start();
}
@FXML
private void onAdd() {
FileChooser chooser = new FileChooser();
chooser.setTitle(Main.i18n("mods.choose_mod"));
chooser.getExtensionFilters().setAll(new FileChooser.ExtensionFilter(Main.i18n("extension.mod"), "*.jar", "*.zip", "*.litemod"));
List<File> res = chooser.showOpenMultipleDialog(Controllers.getStage());
// It's guaranteed that succeeded and failed are thread safe here.
List<String> succeeded = new LinkedList<>();
List<String> failed = new LinkedList<>();
if (res == null) return;
Task.of(variables -> {
for (File file : res) {
try {
modManager.addMod(versionId, file);
succeeded.add(file.getName());
} catch (Exception e) {
Logging.LOG.log(Level.WARNING, "Unable to add mod " + file, e);
failed.add(file.getName());
// Actually addMod will not throw exceptions because FileChooser has already filtered files.
}
}
}).with(Task.of(Schedulers.javafx(), variables -> {
List<String> prompt = new LinkedList<>();
if (!succeeded.isEmpty())
prompt.add(Main.i18n("mods.add.success", String.join(", ", succeeded)));
if (!failed.isEmpty())
prompt.add(Main.i18n("mods.add.failed", String.join(", ", failed)));
Controllers.dialog(String.join("\n", prompt), Main.i18n("mods.add"));
loadMods(modManager, versionId);
})).start();
}
public void setParentTab(JFXTabPane parentTab) {
this.parentTab = parentTab;
}
}
@@ -0,0 +1,57 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXCheckBox;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.layout.BorderPane;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.mod.ModInfo;
import java.util.function.Consumer;
public final class ModItem extends BorderPane {
public ModItem(ModInfo info, Consumer<ModItem> deleteCallback) {
JFXCheckBox chkEnabled = new JFXCheckBox();
BorderPane.setAlignment(chkEnabled, Pos.CENTER);
setLeft(chkEnabled);
TwoLineListItem modItem = new TwoLineListItem();
BorderPane.setAlignment(modItem, Pos.CENTER);
setCenter(modItem);
JFXButton btnRemove = new JFXButton();
FXUtils.installTooltip(btnRemove, Main.i18n("mods.remove"));
btnRemove.setOnMouseClicked(e -> deleteCallback.accept(this));
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.close("black", 15, 15));
setRight(btnRemove);
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
modItem.setTitle(info.getFileName());
modItem.setSubtitle(info.getName() + ", " + Main.i18n("archive.version") + ": " + info.getVersion() + ", " + Main.i18n("archive.game_version") + ": " + info.getGameVersion() + ", " + Main.i18n("archive.author") + ": " + info.getAuthors());
chkEnabled.setSelected(info.isActive());
chkEnabled.selectedProperty().addListener((a, b, newValue) ->
info.activeProperty().set(newValue));
}
}
@@ -1,124 +0,0 @@
<?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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</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(&quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="btnOKActionPerformed"/>
</Events>
</Component>
</SubComponents>
</Form>
@@ -1,164 +0,0 @@
/*
* 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
}
@@ -1,59 +0,0 @@
/*
* 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,124 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.ui.construct.FileItem;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.StringUtils;
import java.io.File;
import java.util.Optional;
public final class ProfilePage extends StackPane implements DecoratorPage {
private final StringProperty title;
private final StringProperty location;
private final Profile profile;
@FXML
private JFXTextField txtProfileName;
@FXML
private FileItem gameDir;
@FXML private JFXButton btnSave;
@FXML private JFXButton btnDelete;
/**
* @param profile null if creating a new profile.
*/
public ProfilePage(Profile profile) {
this.profile = profile;
title = new SimpleStringProperty(this, "title",
profile == null ? Main.i18n("profile.new") : Main.i18n("profile") + " - " + profile.getName());
location = new SimpleStringProperty(this, "location",
Optional.ofNullable(profile).map(Profile::getGameDir).map(File::getAbsolutePath).orElse(""));
FXUtils.loadFXML(this, "/assets/fxml/profile.fxml");
txtProfileName.setText(Optional.ofNullable(profile).map(Profiles::getProfileDisplayName).orElse(""));
FXUtils.onChangeAndOperate(txtProfileName.textProperty(), it -> {
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
});
gameDir.setProperty(location);
FXUtils.onChangeAndOperate(location, it -> {
btnSave.setDisable(!txtProfileName.validate() || StringUtils.isBlank(getLocation()));
});
if (profile == null)
btnDelete.setVisible(false);
}
@FXML
private void onDelete() {
if (profile != null) {
Settings.INSTANCE.deleteProfile(profile);
Controllers.navigate(null);
}
}
@FXML
private void onSave() {
if (profile != null) {
profile.setName(txtProfileName.getText());
if (StringUtils.isNotBlank(getLocation()))
profile.setGameDir(new File(getLocation()));
} else {
if (StringUtils.isBlank(getLocation())) {
gameDir.onExplore();
}
Settings.INSTANCE.putProfile(new Profile(txtProfileName.getText(), new File(getLocation())));
}
Settings.INSTANCE.onProfileLoading();
Controllers.navigate(null);
}
public String getTitle() {
return title.get();
}
@Override
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public String getLocation() {
return location.get();
}
public StringProperty locationProperty() {
return location;
}
public void setLocation(String location) {
this.location.set(location);
}
}
@@ -1,58 +0,0 @@
/*
* 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;
}
}
@@ -0,0 +1,128 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.shape.SVGPath;
public final class SVG {
private SVG() {
}
private static Node createSVGPath(String d, String fill, double width, double height) {
SVGPath path = new SVGPath();
path.getStyleClass().add("svg");
path.setContent(d);
path.setStyle("-fx-fill: " + fill + ";");
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(String 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(String 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(String 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(String 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(String 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(String 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(String 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(String 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(String 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(String 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 script(String 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(String 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(String 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(String 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(String 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(String 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(String 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(String 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(String 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(String fill, double width, double height) {
return createSVGPath("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z", fill, width, height);
}
public static Node plus(String fill, double width, double height) {
return createSVGPath("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z", fill, width, height);
}
}
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
*
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -15,31 +15,29 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.util;
package org.jackhuang.hmcl.ui;
import java.io.File;
import javax.swing.filechooser.FileFilter;
import org.jackhuang.hmcl.util.sys.FileUtils;
import javafx.util.StringConverter;
import org.jackhuang.hmcl.util.Lang;
import java.util.Optional;
/**
*
* @author huangyuhui
*/
public class FileNameFilter extends FileFilter {
public final class SafeIntStringConverter extends StringConverter<Number> {
public static final SafeIntStringConverter INSTANCE = new SafeIntStringConverter();
String acceptedName;
public FileNameFilter(String acceptedName) {
this.acceptedName = acceptedName;
private SafeIntStringConverter() {
}
@Override
public boolean accept(File f) {
return f.isDirectory() || f.getName().equals(acceptedName) && !FileUtils.getExtension(f.getName()).equals("lnk");
public Number fromString(String string) {
return Optional.ofNullable(string).map(Lang::toIntOrNull).orElse(null);
}
@Override
public String getDescription() {
return acceptedName;
public String toString(Number object) {
return Optional.ofNullable(object).map(Object::toString).orElse("");
}
}
@@ -0,0 +1,276 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.Lang;
import java.net.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
public final class SettingsPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", Main.i18n("settings.launcher"));
@FXML
private JFXTextField txtProxyHost;
@FXML
private JFXTextField txtProxyPort;
@FXML
private JFXTextField txtProxyUsername;
@FXML
private JFXPasswordField txtProxyPassword;
@FXML
private JFXTextField txtFontSize;
@FXML
private JFXComboBox<Label> cboLanguage;
@FXML
private JFXComboBox<?> cboDownloadSource;
@FXML
private FontComboBox cboFont;
@FXML
private FileItem fileCommonLocation;
@FXML
private Label lblDisplay;
@FXML
private Label lblUpdate;
@FXML
private Label lblUpdateSub;
@FXML
private JFXButton btnUpdate;
@FXML
private ScrollPane scroll;
@FXML
private MultiFileItem backgroundItem;
@FXML
private MultiColorItem themeItem;
@FXML
private JFXRadioButton chkNoProxy;
@FXML
private JFXRadioButton chkManualProxy;
@FXML
private JFXRadioButton chkProxyHttp;
@FXML
private JFXRadioButton chkProxySocks;
@FXML
private JFXCheckBox chkProxyAuthentication;
@FXML
private GridPane authPane;
@FXML
private Pane proxyPane;
{
FXUtils.loadFXML(this, "/assets/fxml/setting.fxml");
FXUtils.smoothScrolling(scroll);
txtProxyHost.setText(Settings.INSTANCE.getProxyHost());
txtProxyHost.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyHost(newValue));
txtProxyPort.setText(Settings.INSTANCE.getProxyPort());
txtProxyPort.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyPort(newValue));
txtProxyUsername.setText(Settings.INSTANCE.getProxyUser());
txtProxyUsername.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyUser(newValue));
txtProxyPassword.setText(Settings.INSTANCE.getProxyPass());
txtProxyPassword.textProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setProxyPass(newValue));
cboDownloadSource.getSelectionModel().select(DownloadProviders.DOWNLOAD_PROVIDERS.indexOf(Settings.INSTANCE.getDownloadProvider()));
cboDownloadSource.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setDownloadProvider(DownloadProviders.getDownloadProvider(newValue.intValue())));
cboFont.getSelectionModel().select(Settings.INSTANCE.getFont().getFamily());
cboFont.valueProperty().addListener((a, b, newValue) -> {
Font font = Font.font(newValue, Settings.INSTANCE.getFont().getSize());
Settings.INSTANCE.setFont(font);
lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";");
});
txtFontSize.setText(Double.toString(Settings.INSTANCE.getFont().getSize()));
txtFontSize.getValidators().add(new Validator(it -> Lang.toDoubleOrNull(it) != null));
txtFontSize.textProperty().addListener((a, b, newValue) -> {
if (txtFontSize.validate()) {
Font font = Font.font(Settings.INSTANCE.getFont().getFamily(), Double.parseDouble(newValue));
Settings.INSTANCE.setFont(font);
lblDisplay.setStyle("-fx-font: " + font.getSize() + " \"" + font.getFamily() + "\";");
}
});
lblDisplay.setStyle("-fx-font: " + Settings.INSTANCE.getFont().getSize() + " \"" + Settings.INSTANCE.getFont().getFamily() + "\";");
ObservableList<Label> list = FXCollections.observableArrayList();
for (Locales.SupportedLocale locale : Locales.LOCALES)
list.add(new Label(locale.getName(Settings.INSTANCE.getLocale().getResourceBundle())));
cboLanguage.setItems(list);
cboLanguage.getSelectionModel().select(Locales.LOCALES.indexOf(Settings.INSTANCE.getLocale()));
cboLanguage.getSelectionModel().selectedIndexProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setLocale(Locales.getLocale(newValue.intValue())));
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
chkProxyHttp.setUserData(Proxy.Type.HTTP);
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
chkProxySocks.setUserData(Proxy.Type.SOCKS);
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
for (Toggle toggle : proxyConfigurationGroup.getToggles())
if (toggle.getUserData() == Settings.INSTANCE.getProxyType())
toggle.setSelected(true);
ToggleGroup hasProxyGroup = new ToggleGroup();
chkNoProxy.setToggleGroup(hasProxyGroup);
chkManualProxy.setToggleGroup(hasProxyGroup);
if (!Settings.INSTANCE.hasProxy())
chkNoProxy.setSelected(true);
else
chkManualProxy.setSelected(true);
proxyPane.disableProperty().bind(chkNoProxy.selectedProperty());
hasProxyGroup.selectedToggleProperty().addListener((a, b, newValue) ->
Settings.INSTANCE.setHasProxy(newValue != chkNoProxy));
proxyConfigurationGroup.selectedToggleProperty().addListener((a, b, newValue) ->
Settings.INSTANCE.setProxyType((Proxy.Type) newValue.getUserData()));
chkProxyAuthentication.setSelected(Settings.INSTANCE.hasProxyAuth());
chkProxyAuthentication.selectedProperty().addListener((a, b, newValue) -> Settings.INSTANCE.setHasProxyAuth(newValue));
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
fileCommonLocation.setProperty(Settings.INSTANCE.commonPathProperty());
FXUtils.installTooltip(btnUpdate, Main.i18n("update.tooltip"));
checkUpdate();
// background
backgroundItem.loadChildren(Collections.singletonList(
backgroundItem.createChildren(Main.i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
));
FXUtils.bindString(backgroundItem.getTxtCustom(), Settings.INSTANCE.backgroundImageProperty());
backgroundItem.setCustomUserData(EnumBackgroundImage.CUSTOM);
backgroundItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Settings.INSTANCE.getBackgroundImageType()).findFirst().ifPresent(it -> it.setSelected(true));
Settings.INSTANCE.backgroundImageProperty().setChangedListener(it -> initBackgroundItemSubtitle());
Settings.INSTANCE.backgroundImageTypeProperty().setChangedListener(it -> initBackgroundItemSubtitle());
initBackgroundItemSubtitle();
backgroundItem.setToggleSelectedListener(newValue ->
Settings.INSTANCE.setBackgroundImageType((EnumBackgroundImage) newValue.getUserData()));
// theme
themeItem.loadChildren(Arrays.asList(
themeItem.createChildren(Main.i18n("color.blue"), Theme.BLUE),
themeItem.createChildren(Main.i18n("color.dark_blue"), Theme.DARK_BLUE),
themeItem.createChildren(Main.i18n("color.green"), Theme.GREEN),
themeItem.createChildren(Main.i18n("color.orange"), Theme.ORANGE),
themeItem.createChildren(Main.i18n("color.purple"), Theme.PURPLE),
themeItem.createChildren(Main.i18n("color.red"), Theme.RED)
));
if (Settings.INSTANCE.getTheme().isCustom())
themeItem.setColor(Color.web(Settings.INSTANCE.getTheme().getColor()));
themeItem.setToggleSelectedListener(newValue -> {
if (newValue.getUserData() != null) {
Settings.INSTANCE.setTheme((Theme) newValue.getUserData());
themeItem.setOnColorPickerChanged(null);
} else {
themeItem.setOnColorPickerChanged(color ->
Settings.INSTANCE.setTheme(Theme.custom(Theme.getColorDisplayName(color))));
}
});
themeItem.getGroup().getToggles().stream().filter(it -> Settings.INSTANCE.getTheme() == it.getUserData() || Settings.INSTANCE.getTheme().isCustom() && themeItem.isCustomToggle(it)).findFirst().ifPresent(it -> it.setSelected(true));
Settings.INSTANCE.themeProperty().setChangedListenerAndOperate(it -> {
if (it.getName().startsWith("#") || it.getName().startsWith("0x"))
themeItem.setSubtitle(it.getName());
else
themeItem.setSubtitle(Main.i18n("color." + it.getName().toLowerCase()));
Controllers.getScene().getStylesheets().setAll(it.getStylesheets());
});
}
private void initBackgroundItemSubtitle() {
switch (Settings.INSTANCE.getBackgroundImageType()) {
case DEFAULT:
backgroundItem.setSubtitle(Main.i18n("launcher.background.default"));
break;
case CUSTOM:
backgroundItem.setSubtitle(Settings.INSTANCE.getBackgroundImage());
break;
}
}
public String getTitle() {
return title.get();
}
@Override
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public void checkUpdate() {
btnUpdate.setVisible(Main.UPDATE_CHECKER.isOutOfDate());
if (Main.UPDATE_CHECKER.isOutOfDate()) {
lblUpdateSub.setText(Main.i18n("update.newest_version", Main.UPDATE_CHECKER.getNewVersion().toString()));
lblUpdateSub.getStyleClass().setAll("update-label");
lblUpdate.setText(Main.i18n("update.found"));
lblUpdate.getStyleClass().setAll("update-label");
} else {
lblUpdateSub.setText(Main.i18n("update.latest"));
lblUpdateSub.getStyleClass().setAll("subtitle-label");
lblUpdate.setText(Main.i18n("update"));
lblUpdate.getStyleClass().setAll();
}
}
@FXML
private void onUpdate() {
Main.UPDATE_CHECKER.checkOutdate();
}
}
@@ -0,0 +1,200 @@
package org.jackhuang.hmcl.ui;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.css.*;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TwoLineListItem extends StackPane {
private final StringProperty title = new SimpleStringProperty(this, "title");
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
private final StyleableObjectProperty<Font> titleFont = new SimpleStyleableObjectProperty<>(StyleableProperties.TITLE_FONT, this, "title-font", Font.font(15));
private final StyleableObjectProperty<Font> subtitleFont = new SimpleStyleableObjectProperty<>(StyleableProperties.SUBTITLE_FONT, this, "subtitle-font", Font.getDefault());
private final StyleableObjectProperty<Paint> titleFill = new SimpleStyleableObjectProperty<>(StyleableProperties.TITLE_FILL, this, "title-fill", Color.BLACK);
private final StyleableObjectProperty<Paint> subtitleFill = new SimpleStyleableObjectProperty<>(StyleableProperties.SUBTITLE_FILL, this, "subtitle-fill", Color.GRAY);
public TwoLineListItem(String titleString, String subtitleString) {
this();
title.set(titleString);
subtitle.set(subtitleString);
}
public TwoLineListItem() {
Label lblTitle = new Label();
lblTitle.textFillProperty().bind(titleFill);
lblTitle.fontProperty().bind(titleFont);
lblTitle.textProperty().bind(title);
Label lblSubtitle = new Label();
lblSubtitle.textFillProperty().bind(subtitleFill);
lblSubtitle.fontProperty().bind(subtitleFont);
lblSubtitle.textProperty().bind(subtitle);
VBox vbox = new VBox();
vbox.getChildren().setAll(lblTitle, lblSubtitle);
getChildren().setAll(vbox);
}
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 Font getTitleFont() {
return titleFont.get();
}
public StyleableObjectProperty<Font> titleFontProperty() {
return titleFont;
}
public void setTitleFont(Font titleFont) {
this.titleFont.set(titleFont);
}
public Font getSubtitleFont() {
return subtitleFont.get();
}
public StyleableObjectProperty<Font> subtitleFontProperty() {
return subtitleFont;
}
public void setSubtitleFont(Font subtitleFont) {
this.subtitleFont.set(subtitleFont);
}
public Paint getTitleFill() {
return titleFill.get();
}
public StyleableObjectProperty<Paint> titleFillProperty() {
return titleFill;
}
public void setTitleFill(Paint titleFill) {
this.titleFill.set(titleFill);
}
public Paint getSubtitleFill() {
return subtitleFill.get();
}
public StyleableObjectProperty<Paint> subtitleFillProperty() {
return subtitleFill;
}
public void setSubtitleFill(Paint subtitleFill) {
this.subtitleFill.set(subtitleFill);
}
@Override
public String toString() {
return getTitle();
}
@Override
public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() {
return getClassCssMetaData();
}
public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
return StyleableProperties.STYLEABLES;
}
private static class StyleableProperties {
private static final FontCssMetaData<TwoLineListItem> TITLE_FONT = new FontCssMetaData<TwoLineListItem>("-jfx-title-font", Font.font(15)) {
@Override
public boolean isSettable(TwoLineListItem control) {
return control.title == null || !control.title.isBound();
}
@Override
public StyleableProperty<Font> getStyleableProperty(TwoLineListItem control) {
return control.titleFontProperty();
}
};
private static final FontCssMetaData<TwoLineListItem> SUBTITLE_FONT = new FontCssMetaData<TwoLineListItem>("-jfx-subtitle-font", Font.getDefault()) {
@Override
public boolean isSettable(TwoLineListItem control) {
return control.subtitle == null || !control.subtitle.isBound();
}
@Override
public StyleableProperty<Font> getStyleableProperty(TwoLineListItem control) {
return control.subtitleFontProperty();
}
};
private static final CssMetaData<TwoLineListItem, Paint> TITLE_FILL = new CssMetaData<TwoLineListItem, Paint>("-jfx-title-fill", StyleConverter.getPaintConverter(), Color.BLACK) {
@Override
public boolean isSettable(TwoLineListItem control) {
return control.titleFill == null || !control.titleFill.isBound();
}
@Override
public StyleableProperty<Paint> getStyleableProperty(TwoLineListItem control) {
return control.titleFillProperty();
}
};
private static final CssMetaData<TwoLineListItem, Paint> SUBTITLE_FILL = new CssMetaData<TwoLineListItem, Paint>("-jfx-subtitle-fill", StyleConverter.getPaintConverter(), Color.GRAY) {
@Override
public boolean isSettable(TwoLineListItem control) {
return control.subtitleFill == null || !control.subtitleFill.isBound();
}
@Override
public StyleableProperty<Paint> getStyleableProperty(TwoLineListItem control) {
return control.subtitleFillProperty();
}
};
private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
private StyleableProperties() {
}
static {
List<CssMetaData<? extends Styleable, ?>> styleables = new ArrayList<>(Node.getClassCssMetaData());
Collections.addAll(styleables, TITLE_FONT);
Collections.addAll(styleables, SUBTITLE_FONT);
STYLEABLES = Collections.unmodifiableList(styleables);
}
}
}
@@ -0,0 +1,126 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import javafx.beans.binding.Bindings;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.Main;
import java.util.Optional;
public final class VersionItem extends StackPane {
@FXML
private Pane icon;
@FXML
private VBox content;
@FXML
private StackPane header;
@FXML
private StackPane body;
@FXML
private JFXButton btnSettings;
@FXML
private JFXButton btnUpdate;
@FXML
private JFXButton btnLaunch;
@FXML
private JFXButton btnScript;
@FXML
private Label lblVersionName;
@FXML
private Label lblGameVersion;
@FXML
private Label lblLibraries;
@FXML
private ImageView iconView;
private EventHandler<? super MouseEvent> launchClickedHandler = null;
public VersionItem() {
FXUtils.loadFXML(this, "/assets/fxml/version-item.fxml");
setEffect(new DropShadow(BlurType.GAUSSIAN, Color.rgb(0, 0, 0, 0.26), 5.0, 0.12, -1.0, 1.0));
btnSettings.setGraphic(SVG.gear("black", 15, 15));
btnUpdate.setGraphic(SVG.update("black", 15, 15));
btnLaunch.setGraphic(SVG.launch("black", 15, 15));
btnScript.setGraphic(SVG.script("black", 15, 15));
FXUtils.installTooltip(btnSettings, Main.i18n("version.settings"));
FXUtils.installTooltip(btnUpdate, Main.i18n("version.update"));
FXUtils.installTooltip(btnLaunch, Main.i18n("version.launch"));
FXUtils.installTooltip(btnScript, Main.i18n("version.launch_script"));
icon.translateYProperty().bind(Bindings.createDoubleBinding(() -> header.getBoundsInParent().getHeight() - icon.getHeight() / 2 - 16, header.boundsInParentProperty(), icon.heightProperty()));
FXUtils.limitSize(iconView, 32, 32);
setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY)
if (e.getClickCount() == 2)
Optional.ofNullable(launchClickedHandler).ifPresent(h -> h.handle(e));
});
}
public void setUpdate(boolean update) {
btnUpdate.setVisible(update);
}
public void setVersionName(String versionName) {
lblVersionName.setText(versionName);
}
public void setGameVersion(String gameVersion) {
lblGameVersion.setText(gameVersion);
}
public void setLibraries(String libraries) {
lblLibraries.setText(libraries);
}
public void setImage(Image image) {
iconView.setImage(image);
}
public void setOnSettingsButtonClicked(EventHandler<? super MouseEvent> handler) {
btnSettings.setOnMouseClicked(handler);
}
public void setOnScriptButtonClicked(EventHandler<? super MouseEvent> handler) {
btnScript.setOnMouseClicked(handler);
}
public void setOnLaunchButtonClicked(EventHandler<? super MouseEvent> handler) {
launchClickedHandler = handler;
btnLaunch.setOnMouseClicked(handler);
}
public void setOnUpdateButtonClicked(EventHandler<? super MouseEvent> handler) {
btnUpdate.setOnMouseClicked(handler);
}
}
@@ -0,0 +1,72 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import javafx.fxml.FXML;
import javafx.geometry.Rectangle2D;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.StackPane;
public final class VersionListItem extends StackPane {
@FXML
private StackPane imageViewContainer;
@FXML
private Label lblVersionName;
@FXML
private Label lblGameVersion;
@FXML
private ImageView imageView;
private Runnable handler;
public VersionListItem(String versionName) {
this(versionName, "");
}
public VersionListItem(String versionName, String gameVersion) {
FXUtils.loadFXML(this, "/assets/fxml/version-list-item.fxml");
lblVersionName.setText(versionName);
lblGameVersion.setText(gameVersion);
FXUtils.limitSize(imageView, 32, 32);
}
@FXML
private void onSettings() {
handler.run();
}
public void setOnSettingsButtonClicked(Runnable handler) {
this.handler = handler;
}
public void setVersionName(String versionName) {
lblVersionName.setText(versionName);
}
public void setGameVersion(String gameVersion) {
lblGameVersion.setText(gameVersion);
}
public void setImage(Image image, Rectangle2D viewport) {
imageView.setImage(image);
imageView.setViewport(viewport);
}
}
@@ -0,0 +1,208 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXListView;
import com.jfoenix.controls.JFXPopup;
import com.jfoenix.controls.JFXTabPane;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.Tab;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.Main;
import org.jackhuang.hmcl.download.game.GameAssetIndexDownloadTask;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.ui.export.ExportWizardProvider;
import org.jackhuang.hmcl.ui.wizard.DecoratorPage;
import org.jackhuang.hmcl.util.FileUtils;
import java.io.File;
public final class VersionPage extends StackPane implements DecoratorPage {
private final StringProperty title = new SimpleStringProperty(this, "title", null);
@FXML
private VersionSettingsController versionSettingsController;
@FXML
private Tab modTab;
@FXML
private ModController modController;
@FXML
private InstallerController installerController;
@FXML
private JFXListView<?> browseList;
@FXML
private JFXListView<?> managementList;
@FXML
private JFXButton btnBrowseMenu;
@FXML
private JFXButton btnDelete;
@FXML
private JFXButton btnManagementMenu;
@FXML
private JFXButton btnExport;
@FXML
private StackPane rootPane;
@FXML
private StackPane contentPane;
@FXML
private JFXTabPane tabPane;
private final JFXPopup browsePopup;
private final JFXPopup managementPopup;
private Profile profile;
private String version;
{
FXUtils.loadFXML(this, "/assets/fxml/version/version.fxml");
getChildren().removeAll(browseList, managementList);
browsePopup = new JFXPopup(browseList);
managementPopup = new JFXPopup(managementList);
FXUtils.installTooltip(btnDelete, Main.i18n("version.manage.remove"));
FXUtils.installTooltip(btnBrowseMenu, Main.i18n("settings.game.exploration"));
FXUtils.installTooltip(btnManagementMenu, Main.i18n("settings.game.management"));
FXUtils.installTooltip(btnExport, Main.i18n("modpack.export"));
}
public void load(String id, Profile profile) {
this.version = id;
this.profile = profile;
title.set(Main.i18n("settings.game") + " - " + id);
versionSettingsController.loadVersionSetting(profile, id);
modController.setParentTab(tabPane);
modTab.setUserData(modController);
modController.loadMods(profile.getModManager(), id);
installerController.loadVersion(profile, id);
}
@FXML
private void onBrowseMenu() {
browseList.getSelectionModel().select(-1);
browsePopup.show(btnBrowseMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
}
@FXML
private void onManagementMenu() {
managementList.getSelectionModel().select(-1);
managementPopup.show(btnManagementMenu, JFXPopup.PopupVPosition.TOP, JFXPopup.PopupHPosition.RIGHT, -12, 15);
}
@FXML
private void onDelete() {
deleteVersion(profile, version);
}
@FXML
private void onExport() {
exportVersion(profile, version);
}
@FXML
private void onBrowse() {
String sub;
switch (browseList.getSelectionModel().getSelectedIndex()) {
case 0:
sub = "";
break;
case 1:
sub = "mods";
break;
case 2:
sub = "coremods";
break;
case 3:
sub = "config";
break;
case 4:
sub = "resourcepacks";
break;
case 5:
sub = "screenshots";
break;
case 6:
sub = "saves";
break;
default:
throw new RuntimeException();
}
FXUtils.openFolder(new File(profile.getRepository().getRunDirectory(version), sub));
}
@FXML
private void onManagement() {
switch (managementList.getSelectionModel().getSelectedIndex()) {
case 0: // rename a version
renameVersion(profile, version);
break;
case 1: // remove a version
deleteVersion(profile, version);
break;
case 2: // redownload asset index
new GameAssetIndexDownloadTask(profile.getDependency(), profile.getRepository().getVersion(version).resolve(profile.getRepository())).start();
break;
case 3: // delete libraries
FileUtils.deleteDirectoryQuietly(new File(profile.getRepository().getBaseDirectory(), "libraries"));
break;
case 4:
throw new Error();
}
}
public String getTitle() {
return title.get();
}
@Override
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public static void deleteVersion(Profile profile, String version) {
Controllers.confirmDialog(Main.i18n("version.manage.remove.confirm", version), Main.i18n("message.confirm"), () -> {
if (profile.getRepository().removeVersionFromDisk(version)) {
profile.getRepository().refreshVersionsAsync().start();
Controllers.navigate(null);
}
}, null);
}
public static void renameVersion(Profile profile, String version) {
Controllers.inputDialog(Main.i18n("version.manage.rename.message"), res -> {
if (profile.getRepository().renameVersion(version, res)) {
profile.getRepository().refreshVersionsAsync().start();
Controllers.navigate(null);
}
});
}
public static void exportVersion(Profile profile, String version) {
Controllers.getDecorator().startWizard(new ExportWizardProvider(profile, version), Main.i18n("modpack.wizard"));
}
}
@@ -0,0 +1,253 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Toggle;
import javafx.scene.image.Image;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import org.jackhuang.hmcl.Main;
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.ui.construct.ComponentList;
import org.jackhuang.hmcl.ui.construct.ImagePickerItem;
import org.jackhuang.hmcl.ui.construct.MultiFileItem;
import org.jackhuang.hmcl.util.*;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
import java.util.logging.Level;
import java.util.stream.Collectors;
public final class VersionSettingsController {
private VersionSetting lastVersionSetting = null;
private Profile profile;
private String versionId;
@FXML private VBox rootPane;
@FXML private ScrollPane scroll;
@FXML private JFXTextField txtWidth;
@FXML private JFXTextField txtHeight;
@FXML private JFXTextField txtMaxMemory;
@FXML private JFXTextField txtJVMArgs;
@FXML private JFXTextField txtGameArgs;
@FXML private JFXTextField txtMetaspace;
@FXML private JFXTextField txtWrapper;
@FXML private JFXTextField txtPrecallingCommand;
@FXML private JFXTextField txtServerIP;
@FXML private ComponentList advancedSettingsPane;
@FXML private JFXComboBox<?> cboLauncherVisibility;
@FXML private JFXCheckBox chkFullscreen;
@FXML private Label lblPhysicalMemory;
@FXML private JFXToggleButton chkNoJVMArgs;
@FXML private JFXToggleButton chkNoCommon;
@FXML private JFXToggleButton chkNoGameCheck;
@FXML private MultiFileItem globalItem;
@FXML private MultiFileItem javaItem;
@FXML private MultiFileItem gameDirItem;
@FXML private JFXToggleButton chkShowLogs;
@FXML private ImagePickerItem iconPickerItem;
@FXML
private void initialize() {
lblPhysicalMemory.setText(Main.i18n("settings.physical_memory") + ": " + OperatingSystem.TOTAL_MEMORY + "MB");
FXUtils.smoothScrolling(scroll);
Task.of(variables -> variables.set("list", JavaVersion.getJREs().values().stream().map(javaVersion ->
javaItem.createChildren(javaVersion.getVersion(), javaVersion.getBinary().getAbsolutePath(), javaVersion)
).collect(Collectors.toList()))).subscribe(Schedulers.javafx(), variables ->
javaItem.loadChildren(variables.<Collection<Node>>get("list"))
);
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS)
javaItem.getExtensionFilters().add(new FileChooser.ExtensionFilter("Java", "java.exe", "javaw.exe"));
gameDirItem.loadChildren(Arrays.asList(
gameDirItem.createChildren(Main.i18n("settings.advanced.game_dir.default"), EnumGameDirectory.ROOT_FOLDER),
gameDirItem.createChildren(Main.i18n("settings.advanced.game_dir.independent"), EnumGameDirectory.VERSION_FOLDER)
));
globalItem.loadChildren(Arrays.asList(
globalItem.createChildren(Main.i18n("settings.type.global"), true),
globalItem.createChildren(Main.i18n("settings.type.special"), false)
));
}
public void loadVersionSetting(Profile profile, String versionId) {
rootPane.getChildren().remove(advancedSettingsPane);
this.profile = profile;
this.versionId = versionId;
VersionSetting versionSetting = profile.getVersionSetting(versionId);
gameDirItem.setDisable(profile.getRepository().isModpack(versionId));
globalItem.setDisable(profile.getRepository().isModpack(versionId));
if (lastVersionSetting != null) {
FXUtils.unbindInt(txtWidth, lastVersionSetting.widthProperty());
FXUtils.unbindInt(txtHeight, lastVersionSetting.heightProperty());
FXUtils.unbindInt(txtMaxMemory, lastVersionSetting.maxMemoryProperty());
FXUtils.unbindString(javaItem.getTxtCustom(), lastVersionSetting.javaDirProperty());
FXUtils.unbindString(gameDirItem.getTxtCustom(), lastVersionSetting.gameDirProperty());
FXUtils.unbindString(txtJVMArgs, lastVersionSetting.javaArgsProperty());
FXUtils.unbindString(txtGameArgs, lastVersionSetting.minecraftArgsProperty());
FXUtils.unbindString(txtMetaspace, lastVersionSetting.permSizeProperty());
FXUtils.unbindString(txtWrapper, lastVersionSetting.wrapperProperty());
FXUtils.unbindString(txtPrecallingCommand, lastVersionSetting.preLaunchCommandProperty());
FXUtils.unbindString(txtServerIP, lastVersionSetting.serverIpProperty());
FXUtils.unbindBoolean(chkFullscreen, lastVersionSetting.fullscreenProperty());
FXUtils.unbindBoolean(chkNoGameCheck, lastVersionSetting.notCheckGameProperty());
FXUtils.unbindBoolean(chkNoCommon, lastVersionSetting.noCommonProperty());
FXUtils.unbindBoolean(chkShowLogs, lastVersionSetting.showLogsProperty());
FXUtils.unbindEnum(cboLauncherVisibility);
}
FXUtils.bindInt(txtWidth, versionSetting.widthProperty());
FXUtils.bindInt(txtHeight, versionSetting.heightProperty());
FXUtils.bindInt(txtMaxMemory, versionSetting.maxMemoryProperty());
FXUtils.bindString(javaItem.getTxtCustom(), versionSetting.javaDirProperty());
FXUtils.bindString(gameDirItem.getTxtCustom(), versionSetting.gameDirProperty());
FXUtils.bindString(txtJVMArgs, versionSetting.javaArgsProperty());
FXUtils.bindString(txtGameArgs, versionSetting.minecraftArgsProperty());
FXUtils.bindString(txtMetaspace, versionSetting.permSizeProperty());
FXUtils.bindString(txtWrapper, versionSetting.wrapperProperty());
FXUtils.bindString(txtPrecallingCommand, versionSetting.preLaunchCommandProperty());
FXUtils.bindString(txtServerIP, versionSetting.serverIpProperty());
FXUtils.bindBoolean(chkFullscreen, versionSetting.fullscreenProperty());
FXUtils.bindBoolean(chkNoGameCheck, versionSetting.notCheckGameProperty());
FXUtils.bindBoolean(chkNoCommon, versionSetting.noCommonProperty());
FXUtils.bindBoolean(chkShowLogs, versionSetting.showLogsProperty());
FXUtils.bindEnum(cboLauncherVisibility, versionSetting.launcherVisibilityProperty());
boolean flag = false;
Toggle defaultToggle = null;
for (Toggle toggle : javaItem.getGroup().getToggles()) {
if (toggle.getUserData() == Lang.invoke(versionSetting::getJavaVersion)) {
toggle.setSelected(true);
flag = true;
} else if (toggle.getUserData() == JavaVersion.fromCurrentEnvironment()) {
defaultToggle = toggle;
}
}
javaItem.setToggleSelectedListener(newValue -> {
if (javaItem.isCustomToggle(newValue)) {
versionSetting.setJava("Custom");
} else {
versionSetting.setJava(((JavaVersion) newValue.getUserData()).getVersion());
}
});
if (!flag) {
Optional.ofNullable(defaultToggle).ifPresent(t -> t.setSelected(true));
}
versionSetting.javaDirProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
versionSetting.javaProperty().setChangedListener(it -> initJavaSubtitle(versionSetting));
initJavaSubtitle(versionSetting);
if (versionSetting.isUsesGlobal())
globalItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Boolean.TRUE).findFirst().ifPresent(it -> it.setSelected(true));
else
globalItem.getGroup().getToggles().stream().filter(it -> it.getUserData() == Boolean.FALSE).findFirst().ifPresent(it -> it.setSelected(true));
globalItem.setToggleSelectedListener(newValue -> {
if ((Boolean) newValue.getUserData())
profile.globalizeVersionSetting(versionId);
else
profile.specializeVersionSetting(versionId);
Platform.runLater(() -> loadVersionSetting(profile, versionId));
});
versionSetting.usesGlobalProperty().setChangedListenerAndOperate(it ->
globalItem.setSubtitle(Main.i18n(versionSetting.isUsesGlobal() ? "settings.type.global" : "settings.type.special")));
gameDirItem.getGroup().getToggles().stream()
.filter(it -> it.getUserData() == versionSetting.getGameDirType())
.findFirst().ifPresent(toggle -> toggle.setSelected(true));
gameDirItem.setCustomUserData(EnumGameDirectory.CUSTOM);
gameDirItem.setToggleSelectedListener(newValue -> versionSetting.setGameDirType((EnumGameDirectory) newValue.getUserData()));
versionSetting.gameDirProperty().setChangedListener(it -> initGameDirSubtitle(versionSetting));
versionSetting.gameDirTypeProperty().setChangedListener(it -> initGameDirSubtitle(versionSetting));
initGameDirSubtitle(versionSetting);
lastVersionSetting = versionSetting;
loadIcon();
}
private void initJavaSubtitle(VersionSetting versionSetting) {
Task.of(variables -> variables.set("java", versionSetting.getJavaVersion()))
.subscribe(Task.of(Schedulers.javafx(),
variables -> javaItem.setSubtitle(variables.<JavaVersion>getOptional("java")
.map(JavaVersion::getBinary).map(File::getAbsolutePath).orElse("Invalid Java Directory"))));
}
private void initGameDirSubtitle(VersionSetting versionSetting) {
gameDirItem.setSubtitle(profile.getRepository().getRunDirectory(versionId).getAbsolutePath());
}
@FXML
private void onShowAdvanced() {
if (!rootPane.getChildren().contains(advancedSettingsPane))
rootPane.getChildren().add(advancedSettingsPane);
else
rootPane.getChildren().remove(advancedSettingsPane);
}
@FXML
private void onExploreIcon() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter(Main.i18n("extension.png"), "*.png"));
File selectedFile = chooser.showOpenDialog(Controllers.getStage());
if (selectedFile != null) {
File iconFile = profile.getRepository().getVersionIcon(versionId);
try {
FileUtils.copyFile(selectedFile, iconFile);
loadIcon();
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Failed to copy icon file from " + selectedFile + " to " + iconFile, e);
}
}
}
private void loadIcon() {
File iconFile = profile.getRepository().getVersionIcon(versionId);
if (iconFile.exists())
iconPickerItem.setImage(new Image("file:" + iconFile.getAbsolutePath()));
else
iconPickerItem.setImage(FXUtils.DEFAULT_ICON);
FXUtils.limitSize(iconPickerItem.getImageView(), 32, 32);
}
}
@@ -0,0 +1,38 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.jackhuang.hmcl.setting.Settings;
public class WebStage extends Stage {
private final WebView webView = new WebView();
public WebStage() {
setScene(new Scene(webView, 800, 480));
getScene().getStylesheets().addAll(Settings.INSTANCE.getTheme().getStylesheets());
getIcons().add(new Image("/assets/img/icon.png"));
}
public WebView getWebView() {
return webView;
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,20 +15,15 @@
* 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;
package org.jackhuang.hmcl.ui.animation;
import org.jackhuang.hmcl.api.event.EventBus;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
/**
*
* @author huang
*/
public class HMCLApi {
/**
* Events.
*/
public static final EventBus EVENT_BUS = new EventBus();
public static VersionNumber HMCL_VERSION;
public interface AnimationHandler {
Duration getDuration();
Pane getCurrentRoot();
Node getPreviousNode();
Node getCurrentNode();
}
@@ -0,0 +1,28 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import java.util.List;
@FunctionalInterface
public interface AnimationProducer {
List<KeyFrame> animate(AnimationHandler handler);
}
@@ -0,0 +1,95 @@
/*
* Hello Minecraft! Launcher.
* Copyright c 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.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;
public enum ContainerAnimations {
NONE(c -> Collections.emptyList()),
/**
* A fade between the old and new view
*/
FADE(c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getPreviousNode().opacityProperty(), 0.0D, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 1.0D, Interpolator.EASE_BOTH)))),
/**
* A zoom effect
*/
ZOOM_IN(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.0D, 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 ->
(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.0D, 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 ->
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 ->
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(AnimationProducer animationProducer) {
this.animationProducer = animationProducer;
}
public AnimationProducer getAnimationProducer() {
return animationProducer;
}
}
@@ -0,0 +1,98 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
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);
}
@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);
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(transition.animate(this));
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> view.getChildren().remove(previousNode)));
nowAnimation.play();
animation = nowAnimation;
}
private void updateContent(Node newView) {
if (view.getWidth() > 0 && view.getHeight() > 0) {
previousNode = currentNode;
if (previousNode == null)
previousNode = NULL;
} else
previousNode = NULL;
if (previousNode == currentNode)
previousNode = NULL;
currentNode = newView;
view.getChildren().setAll(previousNode, currentNode);
}
private static final StackPane NULL = new StackPane();
}
@@ -0,0 +1,85 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.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)
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();
}
}
@@ -0,0 +1,54 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Node;
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;
/**
* @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 Node getContent() {
return content;
}
}
@@ -0,0 +1,113 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.beans.DefaultProperty;
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.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
@DefaultProperty("content")
public class ComponentList extends StackPane {
private final VBox vbox = new VBox();
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() {
getChildren().setAll(vbox);
content.addListener((ListChangeListener<? super Node>) change -> {
while (change.next()) {
for (int i = change.getFrom(); i < change.getTo(); ++i)
addChildren(change.getList().get(i));
}
});
getStyleClass().add("options-list");
}
public void addChildren(Node node) {
if (node instanceof ComponentList) {
node.getProperties().put("title", ((ComponentList) node).getTitle());
node.getProperties().put("subtitle", ((ComponentList) node).getSubtitle());
}
StackPane child = new StackPane();
child.getChildren().add(new ComponentListCell(node));
if (vbox.getChildren().isEmpty())
child.getStyleClass().add("options-list-item-ahead");
else
child.getStyleClass().add("options-list-item");
vbox.getChildren().add(child);
}
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;
}
}
@@ -0,0 +1,162 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.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.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
/**
* @author huangyuhui
*/
public class ComponentListCell extends StackPane {
private final Node content;
private Animation expandAnimation;
private Rectangle clipRect;
private final BooleanProperty expanded = new SimpleBooleanProperty(this, "expanded", false);
public 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");
StackPane groupNode = new StackPane();
groupNode.getStyleClass().add("options-list-item-header");
Node expandIcon = SVG.expand("black", 10, 10);
JFXButton expandButton = new JFXButton();
expandButton.setGraphic(expandIcon);
expandButton.getStyleClass().add("options-list-item-expand-button");
StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT);
VBox labelVBox = new VBox();
Label label = new Label();
label.textProperty().bind(list.titleProperty());
label.setMouseTransparent(true);
labelVBox.getChildren().add(label);
if (list.isHasSubtitle()) {
Label subtitleLabel = new Label();
subtitleLabel.textProperty().bind(list.subtitleProperty());
subtitleLabel.setMouseTransparent(true);
subtitleLabel.getStyleClass().add("subtitle-label");
labelVBox.getChildren().add(subtitleLabel);
}
StackPane.setAlignment(labelVBox, Pos.CENTER_LEFT);
groupNode.getChildren().setAll(labelVBox, expandButton);
VBox container = new VBox();
container.setStyle("-fx-padding: 8 0 0 0;");
FXUtils.setLimitHeight(container, 0);
Rectangle clipRect = new Rectangle();
clipRect.widthProperty().bind(container.widthProperty());
clipRect.heightProperty().bind(container.heightProperty());
container.setClip(clipRect);
container.getChildren().setAll(content);
VBox holder = new VBox();
holder.getChildren().setAll(groupNode, container);
holder.getStyleClass().add("options-list-item-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(holder);
} else
getChildren().setAll(content);
}
public boolean isExpanded() {
return expanded.get();
}
public BooleanProperty expandedProperty() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded.set(expanded);
}
}
@@ -0,0 +1,123 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXButton;
import javafx.beans.property.Property;
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.Main;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import java.io.File;
public class FileItem extends BorderPane {
private Property<String> property;
private final Label x = 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");
public FileItem() {
VBox left = new VBox();
Label name = new Label();
name.textProperty().bind(nameProperty());
x.getStyleClass().addAll("subtitle-label");
left.getChildren().addAll(name, x);
setLeft(left);
JFXButton right = new JFXButton();
right.setGraphic(SVG.pencil("black", 15, 15));
right.getStyleClass().add("toggle-icon4");
right.setOnMouseClicked(e -> onExplore());
FXUtils.installTooltip(right, Main.i18n("button.edit"));
setRight(right);
Tooltip tip = new Tooltip();
tip.textProperty().bind(tooltipProperty());
Tooltip.install(this, tip);
}
public void onExplore() {
DirectoryChooser chooser = new DirectoryChooser();
if (property.getValue() != null) {
File file = new File(property.getValue());
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)
property.setValue(selectedDir.getAbsolutePath());
chooser.titleProperty().unbind();
}
public void setProperty(Property<String> property) {
this.property = property;
x.textProperty().bind(property);
}
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);
}
}
@@ -0,0 +1,48 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2018 huangyuhui <huanghongxun2008@126.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see {http://www.gnu.org/licenses/}.
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXComboBox;
import javafx.beans.NamedArg;
import javafx.collections.FXCollections;
import javafx.scene.control.ListCell;
import javafx.scene.text.Font;
public class FontComboBox extends JFXComboBox<String> {
public FontComboBox(@NamedArg(value = "fontSize", defaultValue = "12.0") double fontSize,
@NamedArg(value = "enableStyle", defaultValue = "false") boolean enableStyle) {
super(FXCollections.observableArrayList(Font.getFamilies()));
valueProperty().addListener((a, b, newValue) -> {
if (enableStyle)
setStyle("-fx-font-family: \"" + newValue + "\";");
});
setCellFactory(listView -> new ListCell<String>() {
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item);
setFont(new Font(item, fontSize));
}
}
});
}
}
Loaded 100 of 894 files, more files were not shown because too many files have changed in this diff. Show more