Compare commits

..
Author SHA1 Message Date
huanghongxun 32d0d6a7ee Add some task test 2019-02-26 15:28:40 +08:00
huanghongxun d9a336e50b Remove redundant codes 2019-02-26 13:29:09 +08:00
huanghongxun 3c958d06c8 Replace scheduler to executor 2019-02-26 11:39:38 +08:00
huanghongxun 15a75a69b3 Fix exception handling 2019-02-26 08:17:46 +08:00
huanghongxun cae32cff4c remove isDependentSucceeded in whenComplete 2019-02-25 23:59:21 +08:00
huanghongxun eeef76594f Rename task.lastException -> task.exception 2019-02-25 22:17:30 +08:00
huanghongxun 8f2a1030bc Refactor Task 2019-02-24 23:27:53 +08:00
huanghongxun dc1df98277 Fix compilation error 2019-02-24 23:13:31 +08:00
huanghongxun def45cc3e4 Fix NumberFormatException when parsing forge version list json 2019-02-24 23:11:09 +08:00
huanghongxun 8007f1958e Rename isDependentsSucceeded to isDependentSucceeded 2019-02-24 17:00:17 +08:00
huanghongxun b22e724a3f Remove SilentException 2019-02-24 12:39:40 +08:00
huanghongxun ed66b016db Rename then-series functions 2019-02-24 12:39:39 +08:00
yushijinhun 34890f21b3 Update issue template 2019-02-24 12:29:58 +08:00
yushijinhun 9f85e4a63b Do not run GameVersion.minecraftVersion on UI thread 2019-02-24 12:25:19 +08:00
yushijinhun 7fcd4d94a6 Merge branch 'javafx' of github.com:huanghongxun/HMCL into javafx 2019-02-23 23:52:26 +08:00
yushijinhun 84fbca7905 Remove unused code 2019-02-23 23:48:23 +08:00
huanghongxun 9add2387b9 Add comments to FileUtils and IOUtils 2019-02-23 23:42:05 +08:00
huanghongxun a388450f94 Remove AutoTypingMap 2019-02-23 22:50:01 +08:00
huanghongxun fff7abc2ac finalized no longer passes AutoTypingMap as callback parameter 2019-02-23 22:50:00 +08:00
yushijinhun e5587418c7 Remove SafeIntStringConverter 2019-02-23 22:36:38 +08:00
yushijinhun 7c89fac1e9 Rename MultiStepBinding->BindingMapping 2019-02-23 22:14:29 +08:00
yushijinhun bc9a9ffb72 rewrite MultiStepBinding.asyncMap 2019-02-23 21:58:44 +08:00
yushijinhun 46d27a04a2 Fix auth server list in add account pane 2019-02-23 21:49:38 +08:00
huanghongxun 0f33981c96 Fix #534 2019-02-23 19:13:24 +08:00
yushijinhun b396e0b208 Fix compilation error 2019-02-23 18:41:39 +08:00
yushijinhun 1bf2f17538 Fix #533 2019-02-23 18:38:43 +08:00
huanghongxun 608a531655 Remove AtomicReference 2019-02-20 20:18:34 +08:00
huanghongxun e57180eb15 remove lastException in task.variables 2019-02-20 20:07:43 +08:00
huanghongxun 3ae826d9ca Fix task.getLastException does not receive subtasks' value 2019-02-20 20:07:11 +08:00
huanghongxun e940845c60 Fix not correct mod installation 2019-02-20 16:40:26 +08:00
huanghongxun d8aa989cc4 Fix #532 2019-02-19 23:15:45 +08:00
huanghongxun 31ed4268b6 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2019-02-19 19:39:23 +08:00
huanghongxun c698eaac6f Add sponsor information 2019-02-19 19:38:37 +08:00
huanghongxun d0dd23fa14 Fix compilation error along with openjfx 2019-02-19 19:36:23 +08:00
yushijinhun c6f1aa21c5 Log exception in login pane 2019-02-19 18:56:36 +08:00
huanghongxun a3dc7798e7 fetch Forge remote version list only from BMCLAPI 2019-02-17 14:01:05 +08:00
huanghongxun 88f6fd1965 Set cursor in button to hand by default 2019-02-17 13:57:49 +08:00
huanghongxun 9062c2e447 Display more friendly error message when fail to install library 2019-02-17 13:45:10 +08:00
huanghongxun e3d2a4bf9d Update JFoenix to fix #523 2019-02-16 01:52:37 +08:00
huanghongxun c2d609cdb4 Use TableView in ModListPage to allow multiple selection 2019-02-15 19:39:43 +08:00
huanghongxun b3721e29c6 Update copyright statement 2019-02-14 16:20:14 +08:00
huanghongxun eab4463c2d HMCLauncher: verify if JVM version is between 8 and 10 2019-02-14 15:34:52 +08:00
yushijinhun 394873864f Fix #526 2019-02-13 20:09:51 +08:00
yushijinhun 758216245f Add missing license headers 2019-02-13 20:05:01 +08:00
huanghongxun 6edfd112a2 Replace "close" icon to "delete" icon on ModItem 2019-02-13 17:51:01 +08:00
huanghongxun 625215b068 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2019-02-13 17:46:18 +08:00
huanghongxun 4759e4957c refactor 2019-02-13 17:46:11 +08:00
huanghongxun b62f9494ae Have a placeholder in the bottom of LIstPage 2019-02-12 21:39:01 +08:00
huanghongxun 12b47e7d51 Better prompt of LibraryDownloadException 2019-02-12 21:30:45 +08:00
huanghongxun 9ced29aa93 Display furnace image as default for modded 1.13 game 2019-02-11 19:53:55 +08:00
huanghongxun 5095640ba2 Report memory usage when OutOfMemoryError occurred. 2019-02-11 19:47:17 +08:00
huanghongxun c889d7cb3f Download missing game jar when launching 2019-02-11 19:36:19 +08:00
huanghongxun 84636f3f50 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2019-02-10 10:51:39 +08:00
huanghongxun 9a6f07d7c9 make DefaultGameRepositoy.readVersionJson nonnull. 2019-02-10 10:51:04 +08:00
huanghongxun e8f5088049 Support automatic installation of Forge 1.13. 2019-02-10 10:49:48 +08:00
yushijinhun 0bee306d67 css clean up 2019-02-09 21:44:14 +08:00
yushijinhun 8d75b2d413 Fix #519 2019-02-09 21:37:26 +08:00
huanghongxun 426ea607e8 HMCLauncher: try bundled JRE 2019-02-08 22:05:16 +08:00
yushijinhun 507cade50d polish authlib-injector 2019-02-08 17:08:23 +08:00
huanghongxun f0c7f54cc6 Redownload asset index when install new game 2019-02-08 15:54:28 +08:00
yushijinhun 62c38482f6 polish 639bc58e84
see https://github.com/huanghongxun/HMCL/commit/639bc58e84b68a7b508ed9ba0a701a13b2fac1b4#r32215378
2019-02-08 14:55:58 +08:00
yushijinhun 0ff6ca18ed Use https for bmclapi 2019-02-08 14:35:16 +08:00
huanghongxun bbe6c388a3 Merge branch 'skin-refactor' into javafx 2019-02-06 22:53:38 +08:00
huanghongxun 639bc58e84 Support version identification of Minecraft 1.14 2019-02-06 21:54:51 +08:00
huanghongxun c0bcafd747 Revert "Use JFXListView in place of custom components to accelerate rendering"
This reverts commit 0813211
2019-02-06 17:14:49 +08:00
huanghongxun bcaa019551 Only maintain game version json with launchwrapper installed.
Forge modlauncher is not supported right now.
2019-02-06 15:14:08 +08:00
huanghongxun 38725abc48 DownloadInfo.url can be null in forge 1.13.2 version json 2019-02-06 13:35:16 +08:00
yushijinhun 00f1173bac Add translation for CharacterDeletedException 2019-02-05 14:56:11 +08:00
yushijinhun 68a02966d0 Skip parsing json file if it does not exist 2019-02-05 14:31:33 +08:00
yushijinhun 4721fcea66 allow characterName/authserverName to be changed at runtime 2019-02-05 02:09:39 +08:00
yushijinhun 5ea63273be check validity of YggdrasilSession response 2019-02-05 01:46:48 +08:00
yushijinhun 7cee25aab2 Fix concurrent problems in TexturesLoader.Selector 2019-02-05 00:15:17 +08:00
yushijinhun 9298f5e030 Account Refactor 2019-02-04 23:12:40 +08:00
yushijinhun 93f943d40c Fix crash in version settings
introduced in d75c3bbf96
2019-02-04 17:06:20 +08:00
Feng Chang f20c11a4e2 Ensure sequential execution of configuration writer threads (#516)
* Ensure sequential execution of configuration writer threads

Signed-off-by: Chang Feng <chang_196700@hotmail.com>

* Change method to add synchronized to action

Signed-off-by: Chang Feng <chang_196700@hotmail.com>
2019-02-04 02:18:24 +08:00
yushijinhun 0c25f31493 fix npe 2019-01-26 22:50:51 +08:00
yushijinhun 8d774e267f Show register link in login dialog
https://github.com/yushijinhun/authlib-injector/issues/25
2019-01-26 15:19:17 +08:00
yushijinhun 6c1de5d485 Improve tooltip UX 2019-01-26 13:33:59 +08:00
yushijinhun 77c86ed3bc Add tooltip for authlib-injector accounts 2019-01-26 13:17:45 +08:00
yushijinhun d75c3bbf96 Refactor TwoLineListItem 2019-01-26 13:06:56 +08:00
huanghongxun 8bb6cb714c use result of prev task instead of AutoTypingMap 2019-01-22 17:15:03 +08:00
huanghongxun 28d002f6d3 Do not schedule task if preExecute and postExecute are not overrided. 2019-01-22 16:46:11 +08:00
huanghongxun 78fcc69d29 Fixed not displaying "Home" icon. Mentioned in #513 2019-01-22 16:28:41 +08:00
huanghongxun cdfae54ab9 Fixed costly time consumption reading level.dat. Mentioned in #512 2019-01-22 16:11:31 +08:00
huanghongxun 0813211c20 Use JFXListView in place of custom components to accelerate rendering 2019-01-22 16:01:00 +08:00
huanghongxun 056a4eead2 Add comments to LiteLoaderInstallTask and OptiFineInstallTask 2019-01-22 15:54:59 +08:00
huanghongxun 2635d31425 Fix not correctly adding "--tweakClass" for MultiMC modpack 2019-01-22 15:52:57 +08:00
huanghongxun 76d35af5b9 MultiMC modpack patch json file may have "+libraries" or "libraries" 2019-01-22 15:51:52 +08:00
huanghongxun bcff1695d3 Refactor MaintainTask 2019-01-22 15:50:49 +08:00
huanghongxun 3d04447c2f Load world asynchronously 2019-01-22 14:59:01 +08:00
huanghongxun 2736b88a10 Add some promise-like method to TaskResult 2019-01-22 14:58:00 +08:00
huanghongxun 0804a2fbf8 Merge TaskCallable and TaskCallable2 2019-01-22 14:56:48 +08:00
huanghongxun 7224ed6a7d Fix missorting optifine versions 2019-01-16 12:34:13 +08:00
huanghongxun c41737364c Prevent from potential conflict that a MultiMC modpack has ".minecraft" and "minecraft" at the same time 2019-01-13 17:10:23 +08:00
huanghongxun 320e579844 Remove game deletion and renaming action in game settings page 2019-01-13 16:55:07 +08:00
huanghongxun fc3f2e045b Retry fetching remote text file when downloading had been terminated 2019-01-13 16:50:54 +08:00
huanghongxun 481b47bf10 Fix #508. MultiMC stores files in ".minecraft" instead 2019-01-13 16:50:21 +08:00
huanghongxun 0fb87f0b1d Accept modpack drag 2019-01-13 11:55:55 +08:00
huanghongxun 1252d7700f Update alert prompt of Forge 2760 2019-01-11 21:23:26 +08:00
huanghongxun 30c2d88f62 Use spinnerPane in ModListPage 2019-01-11 21:18:26 +08:00
huanghongxun 718a98d7ec Fix alerting Forge conflicts with LiteLoader but actually not 2019-01-11 21:17:02 +08:00
huanghongxun fb3ba220b6 Fix redownloading disabled mods for curse modpack 2019-01-09 13:18:04 +08:00
yushijinhun d655c9ec6b Use /* instead of /** for license header
see: https://github.com/mycila/license-maven-plugin/issues/118
2019-01-05 19:04:24 +08:00
yushijinhun 8a67f33cf6 Update license year to 2019 2019-01-05 18:50:16 +08:00
yushijinhun 2e53688c52 Add gradle default tasks: clean, build 2019-01-05 18:49:28 +08:00
yushijinhun 11ce04fba4 Add dependency org.jetbrains.annotations 2019-01-05 18:49:02 +08:00
yushijinhun 8b191e4a3b Fix grey auth server list cell 2018-12-31 19:43:36 +08:00
huanghongxun 6405263616 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-12-30 19:29:27 +08:00
huanghongxun a2119ab008 Remove "MCBBS" in introduction to the functinoality of modpack install 2018-12-30 19:28:53 +08:00
Haowei Wen b21d8f094a Merge pull request #504 from xfl03/xfl03-jfxpatch-1
Fixed #503
2018-12-23 02:53:10 +08:00
Alexander Xia 90c41a83b1 Fixed #503 2018-12-23 02:49:18 +08:00
huanghongxun a1ec774089 Remind user to move files when switch game working directory 2018-12-22 00:56:05 +08:00
huanghongxun 631056064a Add tooltip about what would be cleaned in game directory 2018-12-22 00:25:19 +08:00
huanghongxun 97e90de71e Add refresh button in mod list page 2018-12-21 01:12:41 +08:00
huanghongxun bbaed35160 Add button to reveal world directory in explorer 2018-12-21 00:40:50 +08:00
huanghongxun 778f26d55f Support manually created world pack 2018-12-21 00:34:46 +08:00
huanghongxun fab5bdc2e1 Auto detect world and datapack's encoding 2018-12-20 20:27:32 +08:00
huanghongxun b7723a88c3 Add dragging world pack to GUI for installation 2018-12-20 20:24:23 +08:00
huanghongxun 1d2b59d5a2 Fix not showing spinner when clicked accept button 2018-12-20 20:07:00 +08:00
huanghongxun 7d42c0f581 Change "redownload asset index" to "Update game asset files" 2018-12-20 19:30:35 +08:00
huanghongxun bfe7398e17 Fixed #501 2018-12-18 13:44:30 +08:00
huanghongxun c4f0a6a197 Suppress IllegalStateException. Closes #500 2018-12-18 13:32:38 +08:00
huanghongxun 1ca9d0fa2c Backup when updating modpack 2018-12-18 13:08:02 +08:00
huanghongxun a015585bd1 Let TaskExecutorDialogPane close itself when finished 2018-12-18 12:35:03 +08:00
huanghongxun bc34427793 Add doDependenciesSucceeded in Task 2018-12-18 00:26:31 +08:00
huanghongxun 0fe867cd93 Fixed #499 2018-12-17 23:31:47 +08:00
yushijinhun 70f0393bde Fix proxy options are passed to game even it's disabled 2018-12-16 16:03:29 +08:00
yushijinhun 9f060be3fd Fix #502
* Check if port is valid before setting it
* Validate user input
* Change proxyPort to int
2018-12-16 15:49:57 +08:00
yushijinhun 176968d883 Fix #498 2018-12-16 00:31:37 +08:00
huanghongxun e6bd55aa69 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-12-13 00:43:15 +08:00
huanghongxun f5ffa875b8 Auto detect encoding of compressed packs 2018-12-13 00:42:52 +08:00
huanghongxun 2cb23cbfcb Log messages at finer and finest level 2018-12-11 15:47:12 +08:00
huanghongxun 31dc9b0925 Delete game whose installation fails 2018-12-11 15:44:17 +08:00
huanghongxun af7cd9a821 Merge pull request #496 from yushijinhun/gpl-additional-terms
Add additional terms under GPLv3 section 7
2018-12-09 00:14:58 +08:00
yushijinhun b34a64f425 Add additional terms under GPLv3 section 7 2018-12-08 23:18:31 +08:00
huanghongxun 11820e31a8 Merge pull request #495 from yushijinhun/license
Update license header
2018-12-08 21:27:10 +08:00
yushijinhun 672b6054a6 Format license headers 2018-12-08 21:13:03 +08:00
yushijinhun d2486732d2 Prevent translation contributors being removed by license-gradle-plugin 2018-12-08 21:11:10 +08:00
yushijinhun 924067e4dd Specify GPL version in the readme link
in case FSF updates it
2018-12-08 21:11:10 +08:00
yushijinhun 7820f748a0 Restore original GPLv3 license
From: https://www.gnu.org/licenses/gpl-3.0.txt
2018-12-08 21:11:10 +08:00
yushijinhun e8757deb0d Use license-gradle-plugin 2018-12-08 21:11:08 +08:00
yushijinhun c371924dc0 Remove Fonts class as it has never been used 2018-12-08 19:39:09 +08:00
yushijinhun cf28b27d50 Remove HMCLCore usage instructions 2018-12-08 19:14:56 +08:00
huanghongxun b78f9c8cd5 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-12-02 20:03:15 +08:00
huanghongxun 91bd67dc26 Restore default cache directory if needed. Closes #489 2018-12-02 20:02:22 +08:00
huanghongxun 0671b49c98 Fix bubble ui 2018-12-02 18:48:22 +08:00
yushijinhun 900f483a03 Fix #493 2018-12-02 14:57:37 +08:00
yushijinhun 4d7af2e68b Fix #492
Related: 4d73187376
2018-12-02 14:54:58 +08:00
yushijinhun db59dcea65 Log exception when failing to load version list
see https://github.com/huanghongxun/HMCL/issues/489#issuecomment-443427026
2018-12-02 13:55:25 +08:00
huanghongxun ef6cfa785f Query cursemeta for removed mods 2018-11-26 00:40:53 +08:00
huanghongxun 2b792e73c9 Fix Java 9 requirement 2018-11-26 00:40:52 +08:00
huanghongxun 0f2922dab4 Update gradle 2018-11-26 00:40:51 +08:00
huanghongxun 45b55e2853 Fix not displaying crash report window when errored in Application.start 2018-11-26 00:40:50 +08:00
huanghongxun 9c7795687f Show installation succeeded when Curse modpack not finished completion 2018-11-26 00:40:49 +08:00
yushijinhun b273c80a02 Add AuthlibInjectorAccount.toString() 2018-11-24 20:43:41 +08:00
yushijinhun 8cdab4710b Add response text to exception message when authlib-injector meta is malformed 2018-11-24 20:33:46 +08:00
yushijinhun ea5b31d537 Allow specifying authlib-injector path 2018-11-24 20:32:22 +08:00
yushijinhun bb0c11fa12 Log authentication exceptions 2018-11-23 23:44:45 +08:00
yushijinhun 37186439cb Fetch authlib-injector metadata on startup 2018-11-23 23:29:41 +08:00
yushijinhun ab9f93d68e Make update checker thread daemon 2018-11-23 23:12:38 +08:00
yushijinhun ca6920e82a Fix preferredLoginType is not exported in modpack 2018-11-23 23:05:24 +08:00
yushijinhun d9db108a31 Use flag instead of accountId to store selected account
Mentioned in #488
2018-11-23 22:57:21 +08:00
yushijinhun 34b7e1d582 Support use authlib-injector offline 2018-11-23 22:01:52 +08:00
yushijinhun 4d73187376 Use @JsonAdapter 2018-11-23 21:49:50 +08:00
yushijinhun dcd0a42705 Refactor AuthlibInjectorServer 2018-11-23 21:34:54 +08:00
yushijinhun 1b40916046 Rewrite config properties 2018-11-23 21:16:05 +08:00
yushijinhun a4e4782f2f Change type from ExceptionalSupplier to AuthlibInjectorDownloader 2018-11-23 17:47:51 +08:00
yushijinhun 875b0c27ef Delete css if bss is generated 2018-11-23 17:08:17 +08:00
huanghongxun a38a1775a0 Update Forge crash detection. Closes #486 2018-11-18 22:09:48 +08:00
huanghongxun fd6ae986c3 Fixed #485 2018-11-13 21:20:14 +08:00
huanghongxun 74a87e65c8 Compile css to bss to speed up launching 2018-11-11 21:12:03 +08:00
yushijinhun a285d2c27d Parallel ca cert check 2018-11-11 11:04:19 +08:00
huanghongxun 44ac3bc900 Fix messy name of installed modpack game 2018-11-06 22:03:43 +08:00
huanghongxun 1c346c97a8 Add an option to skip JVM compatibility 2018-10-31 22:36:35 +08:00
yushijinhun d890b41380 Refactor Accounts to support login type name 2018-10-28 01:08:36 +08:00
yushijinhun cbcc89b48f Refactor RipplerContainer.StyleableProperties 2018-10-27 20:38:55 +08:00
yushijinhun 521bd00a70 Refactor TwoLineListItem.StyleableProperties 2018-10-27 20:16:05 +08:00
yushijinhun b4693423ef Logs the exception if auth server failed to be added 2018-10-27 19:38:30 +08:00
yushijinhun 594a2741d7 Remember the last used login type 2018-10-27 19:30:56 +08:00
yushijinhun a1d9b52b9d Refactor uiVersion 2018-10-27 19:10:02 +08:00
huanghongxun c9d825b6c1 Add a button for cleaning crash-reports and logs manually 2018-10-23 14:08:51 +08:00
huanghongxun 7dcd0eb616 Only display installer when game repository finished loading 2018-10-23 13:52:47 +08:00
huanghongxun e79a95b885 Fix NPE when version json missing "libraries" 2018-10-23 13:42:16 +08:00
huanghongxun 5d232c3f5e Fix NPE when app stopped 2018-10-23 13:36:45 +08:00
huanghongxun ee1c0d362d Fix NPE 2018-10-23 13:34:37 +08:00
huanghongxun aafcd57c82 Attempt to solve #481 2018-10-23 13:32:25 +08:00
huanghongxun 66a4566dc5 Update JFoenix.jar 2018-10-21 11:26:07 +08:00
huanghongxun 4c317f5b03 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-10-21 11:05:33 +08:00
huanghongxun 12d706a812 Don't show AuthenticationException class name 2018-10-21 11:05:27 +08:00
yushijinhun c006068580 Keep 'Next' button and 'Cancel' button on the same line 2018-10-20 21:33:34 +08:00
yushijinhun f8a838506d Replace 'Open logs folder' with 'Export logs'
Related: 8905f2e855
2018-10-20 17:52:06 +08:00
yushijinhun 270844bbb1 Start logging earlier 2018-10-20 17:44:43 +08:00
yushijinhun c9466cf8ef Move MINECRAFT_DIRECTORY & HMCL_DIRECTORY to Metadata 2018-10-20 17:43:35 +08:00
yushijinhun 7d6f1f98f1 Fix verison comparing
Related: fb9418dead
2018-10-20 15:56:14 +08:00
huanghongxun fb9418dead Fixed NPE 2018-10-17 13:51:49 +08:00
huanghongxun 4a1b06b80c Update I18N 2018-10-16 20:32:52 +08:00
huanghongxun 8905f2e855 Add a button to open log directory 2018-10-16 00:20:56 +08:00
huanghongxun 6c74364fa5 Fix writeText 2018-10-15 22:27:26 +08:00
huanghongxun f0720c1755 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-10-15 22:08:31 +08:00
huanghongxun f1acc1d2c1 Fix NPE 2018-10-15 13:05:32 +08:00
Haowei Wen 82bddbfa6e Merge pull request #474 from outloudvi/patch-1
Fix opening resourcepacks/ while opening saves/
2018-10-15 00:26:53 +08:00
Outvi V 841755fe56 Fix opening resourcepacks/ while opening saves/ 2018-10-14 22:39:22 +08:00
huanghongxun 340ee751e8 Suppress VersionNotFoundException 2018-10-14 19:10:04 +08:00
huanghongxun cf70f72f9c Fix #471 2018-10-14 19:04:27 +08:00
huanghongxun 16ea88a659 Fixed failing to download file when cached entry was removed by user 2018-10-14 19:04:21 +08:00
huanghongxun eaa6790013 Update welcome.png 2018-10-14 18:50:47 +08:00
huanghongxun af571d533d Maintain statement when no account 2018-10-14 12:45:52 +08:00
huanghongxun 004c3c588a Log navigation to catch bugs 2018-10-14 12:39:22 +08:00
huanghongxun 2a44167c22 Suppress NPE 2018-10-14 12:39:03 +08:00
huanghongxun cf6b735026 Removed JFXDrawer and JFXHamburger in JFoenix.jar 2018-10-14 12:20:03 +08:00
huanghongxun 5918b04b48 Prevent from calling register and fireEvent at the same time 2018-10-13 11:17:16 +08:00
huanghongxun 8004cb3a64 Change default red to darker one 2018-10-11 22:26:50 +08:00
huanghongxun 9981423927 Change separator fill in title bar and launch button to reduce color difference 2018-10-11 22:20:41 +08:00
huanghongxun 0e5250dfb3 Satisfy requirements metioned in #469 2018-10-11 22:06:34 +08:00
huanghongxun 2dc956b357 Fix request mojang server for skin info every time 2018-10-11 22:05:24 +08:00
huanghongxun b8bd0a08e0 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-10-11 14:05:35 +08:00
huanghongxun fb31eebcf2 Add a warning that forge 2760 is not compatible with liteloader 2018-10-11 14:04:52 +08:00
huanghongxun 3c8689a3f1 Close launching progress dialog when game crashed before opening window 2018-10-11 13:51:37 +08:00
huanghongxun 2957d66dd6 Add test game button in versions page 2018-10-11 13:41:11 +08:00
huanghongxun 6f6efe0874 Rename DecoratorControl to Decorator 2018-10-11 13:32:39 +08:00
huanghongxun 355fc04069 Drop modpack file to install 2018-10-11 13:32:11 +08:00
huanghongxun b4738f383b Fix not on FX thread 2018-10-11 12:59:06 +08:00
huanghongxun b12711969c Rewrite encode location 2018-10-10 16:37:04 +08:00
huanghongxun 94727353c4 Fix response code 400 when downloading mods from curse 2018-10-10 15:27:27 +08:00
huanghongxun 5eb7449b74 Delete old files if etag was updated 2018-10-10 01:36:31 +08:00
huanghongxun dae41dfb30 Add an entrance directly to add authlib-injector server 2018-10-09 22:15:46 +08:00
huanghongxun 07a701e572 Cache asset indexes by ETag cache 2018-10-09 20:22:54 +08:00
huanghongxun 5323aaed6d ETag caching 2018-10-09 20:16:16 +08:00
huanghongxun daf659373f Removed static login method 2018-10-08 21:23:01 +08:00
huanghongxun 0eb81c29df Update I18N 2018-10-08 01:31:02 +08:00
huanghongxun 8aea89c103 Login when launcher opened to reduce launching time 2018-10-08 01:27:48 +08:00
huanghongxun f1d97454c2 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-10-06 12:42:50 +08:00
huanghongxun ffb6a45e28 Remove toLowerCase 2018-10-06 12:42:30 +08:00
huanghongxun f0d1c9b361 Merge pull request #468 from yushijinhun/authlib-injector-ali
支持 authlib-injector API 地址指示
2018-10-06 12:38:04 +08:00
huanghongxun aedd4c290e Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-10-06 12:20:48 +08:00
huanghongxun 749ecfe37f Fix #467 2018-10-06 12:20:42 +08:00
yushijinhun c51da28e31 Add error message for authlib-injector download failure 2018-10-06 12:06:31 +08:00
yushijinhun f3dca75a10 Do not check for update of authlib-injector each time 2018-10-06 11:56:02 +08:00
yushijinhun c3dcb0292c Support -Dauthlibinjector.side=client argument
See yushijinhun/authlib-injector#16
2018-10-06 11:38:55 +08:00
yushijinhun 2a42ecda78 Support authlib-injector ALI
See yushijinhun/authlib-injector#18
2018-10-06 00:47:36 +08:00
huanghongxun 08be1896ee Forbid adding profiles with already existent name 2018-10-05 23:04:56 +08:00
huanghongxun 6b3152c4b5 Try to fix #462. 2018-10-05 22:27:19 +08:00
yushijinhun 19ea27dece emmm... Forgot to add DialogAware.java
Related: b9e26a4112
2018-10-04 12:48:29 +08:00
yushijinhun b9e26a4112 Add DialogAware & Improve add auth server dialog 2018-10-04 12:45:04 +08:00
yushijinhun a71aea8e43 Update translation: account.failed.server_response_malformed 2018-10-04 11:56:12 +08:00
yushijinhun 1b1f22b9fa Fix Java list display error on JDK10
如何重现:
1. 关闭当前选中版本的版本特定设置
2. 重启HMCL
3. 从主界面进入版本设置
4. 打开版本特定设置
5. 展开Java列表

问题:Java列表中的项目除了自定义项外,在外观上仍保持被禁用的状态(但是可以点击)。

原因未知。

此问题只在Java10上出现。
若在进入版本设置界面前,版本特定设置已经被打开,则不会出现此问题。

已知Java项目的styleClass、pseudoClass、disable、disabled属性均正常。

试过但没用的解决方法:
1. 删除HMCL全部CSS
2. 将Java列表默认展开
3. 使用JavaFX原生组件替代JFoenix组件

本提交中的代码由于玄学原因,可以解决此问题。
2018-10-03 22:40:17 +08:00
yushijinhun 705a5659b6 Fix cursor doesn't change when hanging on proxy settings 2018-10-03 21:41:22 +08:00
yushijinhun 4121921aca Fix compile errors 2018-10-03 19:20:47 +08:00
yushijinhun 8cb4ca0e8b Remove Settings.font 2018-10-03 17:17:16 +08:00
yushijinhun 6205937a10 Refactor update channel selecting 2018-10-03 17:15:42 +08:00
yushijinhun 2f522b744f Add MappedProperty & reservedSelectedPropertyFor(CheckBox) 2018-10-03 17:09:12 +08:00
yushijinhun b8a84f831a Refactor proxy settings 2018-10-03 16:43:48 +08:00
yushijinhun 5f9490d780 Add SafeStringConverter & Refactor font settings 2018-10-03 16:38:21 +08:00
huanghongxun a1c5b87b55 Fix crash when no game installed 2018-10-03 15:55:38 +08:00
yushijinhun 641e308c53 Remove right graphic of Game List&Launcher Settings 2018-10-03 13:40:09 +08:00
yushijinhun 64bc70a15d Fix Account/VersionListItem can be unselected 2018-10-03 13:01:30 +08:00
yushijinhun acadbdb5c9 Fix potential NPE
Related: 3323ea2be0
2018-10-03 12:10:19 +08:00
yushijinhun 3323ea2be0 Remove JFXButton in AdvancedListItem 2018-10-03 12:02:17 +08:00
huanghongxun 45f55b2075 Adjust looking of LeftPaneController 2018-10-03 01:32:55 +08:00
yushijinhun 372005dae1 Partially revert 223c2bc7c7 2018-10-02 22:07:57 +08:00
yushijinhun 2c7ca6d354 Remove some methods in Lang 2018-10-02 22:01:42 +08:00
yushijinhun e21f315239 Fix VersionNumber.equals() and hashCode() may not be consistent 2018-10-02 21:25:29 +08:00
yushijinhun 223c2bc7c7 Change update.bubble.subtitle to 'Click here to upgrade' 2018-10-02 21:15:14 +08:00
yushijinhun 3facbba4ee Use databind for update bubble 2018-10-02 21:15:14 +08:00
yushijinhun b8bf56c8c3 Add pseudo class 'first' for 'options-list-item'
Related: caf85d2a92
2018-10-02 20:38:41 +08:00
huanghongxun 2fa08e0a70 Change default profile directory to ".minecraft" 2018-10-02 18:34:09 +08:00
huanghongxun 8e1e13058b Fix #463 2018-10-02 18:33:48 +08:00
huanghongxun 92520e46a3 Remove settings entrance in main page 2018-10-02 18:11:52 +08:00
huanghongxun 6ae6c9c971 Name changes 2018-10-02 18:11:13 +08:00
huanghongxun caf85d2a92 Fix showing a gray horizontal line in component list cell 2018-10-02 18:06:24 +08:00
huanghongxun 030b577dee Add an explicit entry to game list page 2018-10-02 12:44:36 +08:00
huanghongxun 83516ba679 Fix #461 2018-10-02 12:15:52 +08:00
huanghongxun c8f74ee8e3 Add test game in game list page 2018-10-02 11:47:53 +08:00
yushijinhun c50e41ab91 Use setItems in DatapackListPage 2018-10-01 21:01:43 +08:00
yushijinhun c03a0feb75 Refactor ProfileList 2018-10-01 20:59:11 +08:00
yushijinhun 7da5b8fbc8 Add AutomatedToggleGroup & Refactor AccountList 2018-10-01 20:39:00 +08:00
yushijinhun 83f7e61d37 Add selectedItemPropertyFor(ToggleGroup) 2018-10-01 20:03:57 +08:00
yushijinhun 86765275c8 Make SelectionModelSelectedItemProperty a util class 2018-10-01 15:38:12 +08:00
yushijinhun a01e15d841 Add MultiStepBinding&ReadWriteComposedProperty 2018-10-01 15:35:39 +08:00
yushijinhun 5e5db332d0 Remove 'Downloads' paragraph in readme, as GitHub Releases is available 2018-10-01 11:29:12 +08:00
huanghongxun 2de2f45124 Fix always showing horizontal scrollbar of game list popup in main page 2018-09-30 20:07:57 +08:00
huanghongxun b1b3eb9369 Fix always using custom common directory 2018-09-30 20:07:57 +08:00
yushijinhun 507665cd91 Fix #460 2018-09-30 19:45:39 +08:00
yushijinhun c779172cc2 Fix custom toggle is not selected when using customized java 2018-09-30 19:30:22 +08:00
yushijinhun 684813e131 Refactor Java lookup 2018-09-30 19:18:29 +08:00
huanghongxun 76ef47efc4 Suppress exceptions thrown when checking Java installation 2018-09-30 13:22:42 +08:00
huanghongxun 71a8fa0e66 Display platform of java installations 2018-09-28 19:30:41 +08:00
huanghongxun 48d98b2424 Conceal NPE 2018-09-26 19:07:09 +08:00
huanghongxun 5a0e17b121 Fix crash when user set common directory to an invalid path 2018-09-26 19:00:31 +08:00
huanghongxun 5f686f7205 Fix crash when modpack to be installed is malformed. 2018-09-26 13:13:44 +08:00
huanghongxun 518969d2f2 Fix JsonParseException 2018-09-26 13:13:21 +08:00
huanghongxun 01ff1327f7 Make head displayed more precisely 2018-09-25 12:30:21 +08:00
huanghongxun 4d366397a8 Merge pull request #457 from andylizi/add-helmet-support
支持在头像中显示头盔层
2018-09-24 17:28:17 +08:00
huanghongxun 80f79d20a5 Update message UI 2018-09-24 17:26:02 +08:00
huanghongxun 1611d11544 Fix crash when yggdrasil server gives response with malformed UUID 2018-09-24 17:24:13 +08:00
andylizi c8e8b25c85 Adjust faceOffset in skin preview 2018-09-24 15:34:48 +08:00
andylizi 92cfc3c1d3 Make skin preview more accurate 2018-09-24 13:47:59 +08:00
huanghongxun c4cebe72b4 Show bubble when updates available 2018-09-24 01:51:14 +08:00
andylizi 6d346a3ad6 Render helmet layer in skin preview. Fixed #456 2018-09-23 22:53:23 +08:00
huanghongxun 6e2696dea3 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-09-22 22:26:14 +08:00
huanghongxun ba67c968cf Change version selection list look 2018-09-22 22:25:53 +08:00
huanghongxun 332719f49a Fix closing page 2018-09-22 21:40:16 +08:00
yushijinhun fb18975c5c Fix param table in readme 2018-09-22 21:08:03 +08:00
yushijinhun 08a7c27353 Fix config file is checked before created 2018-09-22 21:05:50 +08:00
yushijinhun 4c9cb99a94 Add hmcl.self_integrity_check.disable option 2018-09-22 21:05:50 +08:00
huanghongxun cbecca5f55 Merge pull request #455 from yushijinhun/javafx
重构
2018-09-22 18:12:18 +08:00
yushijinhun 4a0831a155 Replace Properties with InvocationDispatcher & Make config saving async 2018-09-22 14:21:17 +08:00
yushijinhun fa38c53915 Remove Constants.SYSTEM_CHARSET 2018-09-22 12:54:15 +08:00
yushijinhun e7c2170ce3 Fix getSteve/AlexSkin ignores scaleRatio param 2018-09-22 12:53:02 +08:00
yushijinhun 9b69fe8c56 Remove Constants.DEFAULT_ICON 2018-09-22 12:52:30 +08:00
yushijinhun ed91771594 Remove truePredicate/falsePredicate/emptyConsumer 2018-09-22 12:39:57 +08:00
yushijinhun b33e014f71 Move GSON const to JsonUtils 2018-09-22 12:37:03 +08:00
yushijinhun 19632ac162 Move JsonUtils->gson, JarUtils->io, Properties->javafx 2018-09-22 12:33:18 +08:00
yushijinhun 74e3a9e2d1 Use http.agent to set useragent 2018-09-22 12:30:33 +08:00
yushijinhun b15291dc57 Refactor util package structure 2018-09-22 12:12:06 +08:00
huanghongxun 1b5d4c1b42 Improve UI details 2018-09-22 00:18:55 +08:00
huanghongxun 923924638e Add a close button to close all pages 2018-09-20 22:54:41 +08:00
huanghongxun 8b52c46664 Merge resources.zip to allow multiple datapack zip containing resources.zip 2018-09-20 00:12:41 +08:00
huanghongxun aa8c386cdd Support zip file in datapacks folder 2018-09-19 23:02:27 +08:00
huanghongxun bafe17107c Fix loading empty repository 2018-09-19 16:31:17 +08:00
huanghongxun 9c169fb560 Add a separator to separate launch button and popup button 2018-09-19 10:46:14 +08:00
huanghongxun 61ab8a6769 Fix NPE when first start 2018-09-19 00:20:32 +08:00
huanghongxun b1cd7057db Add a convenient entrance to launch other version 2018-09-19 00:16:21 +08:00
huanghongxun d426952d47 Fix Minecraft jar does not exist when renaming a version 2018-09-19 00:12:44 +08:00
huanghongxun 67b6a60382 Fix bugs with world. 2018-09-17 16:30:27 +08:00
huanghongxun 9d5da5a39d Try again when decorator is not initialized. 2018-09-17 16:28:52 +08:00
huanghongxun efb14572d6 Fix selected profile automatically changes to Home 2018-09-17 16:28:01 +08:00
huanghongxun bc3ae5cc7d Add link to hmcl documentation page 2018-09-16 14:12:52 +08:00
huanghongxun 9752dea0e1 Merge pull request #454 from yushijinhun/javafx
重构
2018-09-16 13:46:31 +08:00
huanghongxun 9afdc99157 Support single datapack zip 2018-09-16 13:15:54 +08:00
huanghongxun 07febc18d4 World and datapacks management 2018-09-16 12:38:15 +08:00
yushijinhun 68fdfb36fd Add selectedItemPropertyFor(ComboBox) 2018-09-16 00:52:03 +08:00
yushijinhun 09287f9c72 Rename WeakListenerHelper to WeakListenerHolder 2018-09-15 23:24:42 +08:00
yushijinhun 4a5ac1b899 Remove Settings.logLines 2018-09-15 23:18:59 +08:00
yushijinhun afad520669 Move ProxyManager.proxyProperty initialization to init() 2018-09-15 23:13:25 +08:00
yushijinhun 9950bef260 Change type of downloadType to string 2018-09-15 23:05:37 +08:00
Yuhui Huang 1753b4d27e Datapack resolving 2018-09-14 15:44:40 +08:00
Yellow Fish 2bfc85b18f Fixed NPE mentioned in #451 2018-09-13 01:13:20 +08:00
Yellow Fish 24f3dec453 Fixed #450 2018-09-12 21:37:27 +08:00
huangyuhui 4e6237c062 Fixed #448 2018-09-11 19:26:36 +08:00
huangyuhui 5cbde9cc33 Fix not on FX user thread 2018-09-09 19:38:14 +08:00
huangyuhui 25d79a94f8 Move specialize and globalize VersionSetting to HMCLGameRepository 2018-09-09 18:55:01 +08:00
huangyuhui a5997be9c4 Fix crash when selectedVersion set to empty string 2018-09-09 18:35:10 +08:00
huangyuhui c679667079 Fix crash when the game version is too long 2018-09-09 18:24:05 +08:00
huangyuhui 6b311c679d Fixed #446 2018-09-08 22:14:06 +08:00
huangyuhui 0f1f881367 Add BuildNumberOffset in gradle build config 2018-09-08 19:41:02 +08:00
huangyuhui 090665528f Improve UI 2018-09-08 19:34:51 +08:00
huangyuhui 387beb8484 Fix not saving profile configurations 2018-09-08 01:03:36 +08:00
huanghongxun daa1a38a63 Refactor ModListPage and InstallerListPage 2018-09-08 00:36:57 +08:00
huangyuhui d8a34aac1c Widen task dialog 2018-09-07 15:16:09 +08:00
huangyuhui ce9d882c9f Add total progress of task execution 2018-09-07 15:12:41 +08:00
huanghongxun 84c18c8693 Move cache repository to HMCLCore 2018-09-07 11:27:14 +08:00
huangyuhui 9360854150 Use ReadWriteLock instead of synchronized 2018-09-07 01:58:43 +08:00
huangyuhui ada63f1452 Fix not selecting the version just downloaded 2018-09-07 01:58:22 +08:00
huangyuhui c3cd0dd8a0 Refactor ProfileList & AccountList 2018-09-07 01:03:59 +08:00
huangyuhui 60143d5299 Refactor FileDownloadTask 2018-09-07 00:35:28 +08:00
huangyuhui 1019c891e2 Cache asset files 2018-09-06 17:47:44 +08:00
huangyuhui 1392ad413a Only cache verified files and copy cached files to current directory 2018-09-06 00:13:16 +08:00
huangyuhui 61aee90c8d Refactor DecoratorPage 2018-09-05 23:25:51 +08:00
huangyuhui 19725012fb Refactor AccountList and ProfileList 2018-09-05 23:20:20 +08:00
huangyuhui 94a71dc88f Fix unclosing file 2018-09-05 22:17:58 +08:00
huangyuhui d8cb93c36a Add Task.preExecute and Task.postExecute 2018-09-05 20:38:00 +08:00
huangyuhui 151097a884 Removed the text that notices user to enable disabling jvm args when using Java 9 2018-09-04 21:42:27 +08:00
huanghongxun e97bad88c6 Fix not syncing observable list 2018-09-04 14:25:18 +08:00
huangyuhui 9f494a35ec Add a button for deleting the game icon 2018-09-03 23:58:23 +08:00
huangyuhui d9d20c969a Refactor ComponentList 2018-09-03 23:42:42 +08:00
huangyuhui 7e08cfedd6 Add Constant Pool Scanner to open source code dependencies 2018-09-03 22:35:25 +08:00
huangyuhui 1145e31162 Extract IconedMenuItem 2018-09-03 22:30:22 +08:00
huangyuhui e4e521c2ef Change Controllers.navigate(null) to PageCloseEvent/onEnd 2018-09-03 21:27:39 +08:00
huangyuhui d01c9a425a Fix crash when removed all profiles 2018-09-03 18:38:40 +08:00
huangyuhui a9be348ddf Add prompt warning user that no game installed 2018-09-03 18:38:39 +08:00
huangyuhui 07079ee295 Fix image mis-positioned in AccountAdvancedListItem 2018-09-03 18:38:39 +08:00
huangyuhui eb1bab453b Cancel the option enabling game list in main page 2018-09-03 18:38:39 +08:00
huangyuhui fd3c496a3d Fix crash when first launch 2018-09-03 18:38:39 +08:00
huanghongxun 35e1e0bdd4 Change Java to 64bit automatically. 2018-09-03 17:33:45 +08:00
huangyuhui 28aa137dc1 Use iconedItem instead of ListView in popup menu 2018-09-03 10:58:45 +08:00
huangyuhui 16ee0a040a optimize import 2018-09-03 00:25:56 +08:00
huangyuhui 437391a860 Merge branch '3.2' into javafx 2018-09-03 00:11:16 +08:00
huangyuhui f61181438f allow displaying game list in main page 2018-09-03 00:10:21 +08:00
huangyuhui d74ab35279 Move package 2018-09-02 23:30:27 +08:00
huangyuhui bbc2b251dd Change the way to determine whether use global settings or the specific one 2018-09-02 23:24:32 +08:00
huangyuhui 222e39dc48 Remove decorator.fxml 2018-09-02 21:58:03 +08:00
huangyuhui f5b310d959 Move AdvancedListItem & Skin to new package 2018-09-02 21:57:25 +08:00
huangyuhui 5fb0035b2f Refactor decorator as well as navigation 2018-09-02 21:53:34 +08:00
huangyuhui b8793f7575 Prevent horizontal scroll bar in settings page 2018-09-02 21:52:45 +08:00
huangyuhui 5c5f091769 Fails game installing when unable to download game jar 2018-09-01 23:41:13 +08:00
huangyuhui cf6485aea8 Make exception message more understandable in FileDownloadTask 2018-09-01 23:38:32 +08:00
huangyuhui f2f0c96e62 Notice user to add authentication server 2018-09-01 23:38:05 +08:00
huangyuhui 64e7cc74b2 ProfileList 2018-09-01 00:36:18 +08:00
huangyuhui 531fd87ff3 Profiles 2018-09-01 00:19:25 +08:00
huangyuhui efa280b839 Move profile management to Profiles 2018-08-31 22:28:06 +08:00
huangyuhui b3d84c4f99 Refactor 2018-08-31 22:21:15 +08:00
huangyuhui 015a1e3482 Fix #441 2018-08-31 12:21:08 +08:00
huangyuhui 5efc8d6fa9 Refactor View and ViewModel to Control and Skin 2018-08-31 12:13:47 +08:00
huangyuhui bb1654828f Change "Run Directory" to "Working Directory" 2018-08-31 10:44:01 +08:00
huangyuhui 93cabde9f4 clean up HMCLCore 2018-08-30 22:20:18 +08:00
huangyuhui aeb5eb9129 clean up HMCL 2018-08-30 22:19:49 +08:00
huangyuhui e9e8b8a1ad Change contact URL 2018-08-30 22:15:12 +08:00
huangyuhui 08564e5ea2 Add account list view 2018-08-30 22:14:30 +08:00
huangyuhui 1695619ef5 Move account ui class to a new package 2018-08-30 22:13:53 +08:00
huangyuhui 8f2f57c543 cut off the version name if too long 2018-08-30 22:13:13 +08:00
huangyuhui 60454c6af3 New game list page 2018-08-30 22:12:39 +08:00
huangyuhui 40c7c72973 Move operations on version to new class 2 2018-08-30 22:11:58 +08:00
huangyuhui f9127272b1 Split download wizard into { new game, install modpack } 2018-08-30 22:11:49 +08:00
huangyuhui ec295664d9 Move operations on version to new class 2018-08-30 22:11:00 +08:00
huangyuhui ec2b9b19be Add an entrance to game list page 2018-08-30 22:09:54 +08:00
huangyuhui 1566e069ed Add selectedMinecraftVersion 2018-08-30 22:09:05 +08:00
huangyuhui 9c149f3529 Postpone loading fonts 2018-08-29 21:05:07 +08:00
huangyuhui 2f35baef39 Convert settings.fxml to Java code 2018-08-29 20:28:43 +08:00
huangyuhui 7c77c317df Improve finalizedTask log 2018-08-29 00:03:50 +08:00
huangyuhui ed3a04d531 Fix animation twinkling 2018-08-28 00:23:49 +08:00
huangyuhui c3ec7741dc Change "Game directory" to "Run directory" 2018-08-28 00:22:12 +08:00
huanghongxun f4f89fac1f Merge pull request #438 from yushijinhun/force-update
移除「更新一次 HMCL,电脑上所有 HMCL 都得到更新」的功能
2018-08-26 19:51:20 +08:00
yushijinhun 5262150d3c Prevent 2.x being updated to 3.x 2018-08-26 19:34:10 +08:00
yushijinhun b5f56eb2b5 Remove force update feature 2018-08-26 19:02:47 +08:00
huangyuhui 9a375fd0d1 Add hiding versions. Closes #437 2018-08-26 11:50:42 +08:00
huangyuhui 6ca4aeb5a2 Support 1.13 auto-installing 2018-08-26 11:48:27 +08:00
huangyuhui 8ed4a9a5f2 Fix not showing Optifine 1.13 alpha 2018-08-25 19:53:29 +08:00
huangyuhui a4578405cf Remove RandomUserAgent 2018-08-25 19:39:00 +08:00
huangyuhui 73f147b7e2 Update Chinese localization 2018-08-23 21:45:56 +08:00
huangyuhui 5c3d735315 Prevent HMCL 2.x updating 2018-08-23 21:45:47 +08:00
huanghongxun 200621996d Merge pull request #431 from pan93412/javafx
Update Chinese (Taiwan) Translation
2018-08-22 20:56:12 +08:00
byStarTW a912a0a07e Update Chinese (Taiwan) Translation 2018-08-22 20:54:49 +08:00
huangyuhui 4146ceb048 Fix not downloading the correct game version 2018-08-22 04:25:35 +08:00
huangyuhui ad568d2b9f Fix #429 2018-08-22 02:08:49 +08:00
huangyuhui c7bfb9a11d Suggest user to install latest Java 8 to avoid potentional game crashing 2018-08-21 21:12:05 +08:00
huangyuhui c9391181bd Display empty message when loading 1.13 forge version list 2018-08-21 19:31:04 +08:00
huangyuhui b08eb519bf Remove logging support which is meaningless 2018-08-21 11:48:19 +08:00
huangyuhui 47c4fc2f7e Fix #426 2018-08-20 11:53:02 +08:00
huangyuhui dfd177433a Fix not replacing tokens metioned in #426 2018-08-20 11:38:06 +08:00
huanghongxun 27381e9696 Merge pull request #425 from yushijinhun/yushijinhun/javafx
修复 UpdateChecker 潜在问题 & 弃用 ImmediateXXXProperty
2018-08-19 19:47:30 +08:00
yushijinhun 1c7827c53c Deprecate and partially remove ImmediateProperty 2018-08-19 15:55:18 +08:00
yushijinhun 9512bdb5d6 Remove UpdateChecker.updateChannel & Fix concurrent problems in update checking 2018-08-19 15:43:34 +08:00
huangyuhui 33d1f1d692 Not showing 'unknown' in mod info 2018-08-19 12:19:11 +08:00
huangyuhui c6b49545ff Fix #424 2018-08-19 12:13:39 +08:00
huangyuhui a4a833bfdb Fix auto-installing: missing checksums in game json 2018-08-19 11:58:02 +08:00
huangyuhui 2e1b6e4630 Fix not correctly loading remote game version list 2018-08-19 11:56:39 +08:00
huangyuhui 6911cfa387 try to move deleting version to trash 2018-08-19 11:34:07 +08:00
huangyuhui aa29f890de Log memory size and metaspace 2018-08-19 02:03:05 +08:00
huangyuhui 0f4e6a01bc Fix unable to install more than one libraries 2018-08-19 01:18:57 +08:00
huangyuhui 1d3d8d6710 Show OptiFine versions in dividually 2018-08-19 00:39:47 +08:00
huangyuhui 005e06c56f Always download Optifine from BMCLAPI 2018-08-19 00:35:37 +08:00
huangyuhui 0a7c32b3cb Friendly prompt warning user about independent version deletion 2018-08-18 22:40:08 +08:00
huangyuhui 82916a04ea Check if forge libraries' checksums are valid 2018-08-18 22:25:13 +08:00
huangyuhui ba2e419894 Fix #422 2018-08-18 22:09:16 +08:00
huangyuhui 0812c9025d Do not check game asset hash code for every launching 2018-08-18 02:00:00 +08:00
huangyuhui 4c59ed3ccf Fix installing MultiMC modpack 2018-08-18 01:12:22 +08:00
huangyuhui 43d8d96608 Add doublequotes when doublequotes exist in windows batch 2018-08-18 00:43:55 +08:00
huangyuhui 26a6a7d883 Add doublequotes when special characters exist in launch script. Closes #420 2018-08-18 00:32:37 +08:00
huangyuhui 2e9c237367 Use system proxy by default 2018-08-17 23:48:01 +08:00
huangyuhui 99d2816ea7 Suppress RuntimeException since -1 is possible 2018-08-17 22:11:46 +08:00
huangyuhui 7c3853b0e6 Not logging command line in ProcessStoppedEvent 2018-08-17 22:10:11 +08:00
huangyuhui 53aecf38e0 Support displaying Rift mod information 2018-08-17 22:09:37 +08:00
huangyuhui 41fab0a781 Log launching game version name 2018-08-17 21:56:52 +08:00
huangyuhui 3df35aa373 Translate ZipError into ZipException since ZipError is not IOException 2018-08-17 21:53:29 +08:00
huangyuhui 543eaa4f66 Force cancel using custom log4j config 2018-08-17 21:52:35 +08:00
huangyuhui 3a851b7f1a Fix unable to launch 1.13 with Rift 2018-08-17 21:51:25 +08:00
huangyuhui 089ae1e313 Suppress InterruptedException 2018-08-17 21:51:12 +08:00
huangyuhui bfe5802d02 Remove roboto font 2018-08-16 02:46:49 +08:00
huangyuhui 3ec6c97380 Replace 2.x with existent 3.x 2018-08-15 01:40:18 +08:00
huangyuhui e376da208d Fix duplicate JavaVersions in Windows. Closes #419 2018-08-14 10:44:24 +08:00
huangyuhui 3b02c5e4e0 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-08-14 00:10:48 +08:00
huangyuhui 1bcc79c415 Fix always trying to close launcher when game version cannot be obtained 2018-08-14 00:09:20 +08:00
huangyuhui 9e2cb25d01 Convert old offline account setings 2018-08-14 00:08:35 +08:00
yushijinhun 7e820ba2a5 Fix closeDialog() throws NPE 2018-08-13 22:18:18 +08:00
huanghongxun b079940cd9 Fix uniquing text2speech and its native library. Closes #418 2018-08-11 09:45:31 +08:00
huanghongxun 593ee24429 Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-08-10 19:05:26 +08:00
huanghongxun f7a91bf10c Fix version setting not saved 2018-08-10 19:04:33 +08:00
huanghongxun dad7ac706f Add update channel selection 2018-08-10 12:53:49 +08:00
Haowei Wen 13c48ce78c Merge pull request #417 from tsl0922/osx-maximized
Mac 下恢复使用自定义窗口边框按钮
2018-08-10 08:26:56 +08:00
Shuanglei Tao 8ecbba1003 Remove native window border on osx 2018-08-09 23:41:56 +08:00
huanghongxun 8bf6a9ec9d Rename AdvancedListItem 2018-08-09 20:22:57 +08:00
huanghongxun 64092a4bd4 Merge branch 'yushijinhun-java' into javafx 2018-08-09 13:54:54 +08:00
yushijinhun 8e6bcd969e Refactor JavaVersion 2018-08-09 13:49:16 +08:00
yushijinhun 6777dd7ea7 Fix UnsupportedOperationException in java lookup
Mentioned in #412
2018-08-09 13:22:49 +08:00
yushijinhun f16885568e Make config files hidden 2018-08-09 12:56:03 +08:00
huanghongxun ab109bcfcc Merge pull request #414 from yushijinhun/javafx
Fixes FileSystemNotFoundException in createZipFileSystem
2018-08-09 11:41:27 +08:00
yushijinhun 17f71eaa21 Fixes FileSystemNotFoundException in createZipFileSystem 2018-08-09 11:34:38 +08:00
huanghongxun 3a294e354a ExeLauncher: Search Java in C:/Program Files 2018-08-08 21:16:57 +08:00
huanghongxun 54814181b9 Change xz compressing level to 6 since CI server does not have enough memory 2018-08-08 12:57:42 +08:00
huanghongxun 935c2bd6e5 Merge branch 'rewrite-upgrade' into javafx 2018-08-08 10:03:14 +08:00
huanghongxun a2b74b8e78 Fix getImplementationVersion returning null on Java 9. 2018-08-08 10:02:34 +08:00
huanghongxun 09730d74e0 Merge branch 'rewrite-upgrade' of https://github.com/yushijinhun/HMCL into rewrite-upgrade 2018-08-07 22:18:58 +08:00
huanghongxun 2bf03291ce Try Java8 for Minecraft 1.12.2 or older modded game 2018-08-07 22:10:34 +08:00
huanghongxun 93e5851167 Improve config compatibility with HMCL 2.x. Fix #411 2018-08-07 18:40:23 +08:00
huanghongxun 61fd581ef5 Hide the separator line between refreshNavButton and window buttons when refreshNavButton is hidden 2018-08-07 18:08:20 +08:00
huanghongxun 51afcf2dee Use ZipFileSystem instead of commons-compress 2018-08-07 11:53:05 +08:00
huanghongxun cadafe13e1 Remove IOUtils.closeQuietly 2018-08-07 11:53:05 +08:00
Haowei Wen 47031cb70e Merge pull request #413 from tsl0922/osx
修复在 OSX 下使用的一些问题
2018-08-06 18:36:53 +08:00
Shuanglei Tao b3e776b4bc Use native window border on osx (workaround for #403) 2018-08-06 14:17:41 +08:00
Shuanglei Tao e57a1a8da6 Make gradlew executable 2018-08-05 23:08:27 +08:00
yushijinhun 7f2a392a33 Use com.github.johnrengelman.shadow plugin 2018-08-05 11:06:50 +08:00
yushijinhun 055379f569 Add .pack.gz build back 2018-08-05 09:37:55 +08:00
yushijinhun 2b8843ca19 Replace .pack.gz with .pack.xz 2018-08-05 01:02:42 +08:00
yushijinhun ace830bced Refactor build.gradle 2018-08-05 00:42:05 +08:00
yushijinhun 3c2f232acb Rename PACK -> PACK_GZ 2018-08-04 23:11:31 +08:00
yushijinhun 86030e64ca Extract writeToStorage() 2018-08-04 23:10:14 +08:00
huanghongxun b1ce3c31f5 Build packed archive 2018-08-04 22:12:02 +08:00
huanghongxun 04553c2c56 Support packed update archive 2018-08-04 22:04:59 +08:00
yushijinhun bdb7afa860 Replace updateSource with updateChannel 2018-08-04 21:39:19 +08:00
yushijinhun 7096135cdf Change update source to hmcl.huangyuhui.net 2018-08-04 21:26:46 +08:00
yushijinhun be37014206 Use Paths.get(url.toURI()) in LocalVersion.current() 2018-08-04 21:17:52 +08:00
yushijinhun b78beb3305 Copy downloaded HMCL to .hmcl only after it's verified 2018-08-01 15:54:52 +08:00
yushijinhun 38f27d134d Change task.test() to executor.test() 2018-08-01 15:54:52 +08:00
yushijinhun 3b42ba9375 Add version query when checking for update 2018-08-01 15:54:52 +08:00
yushijinhun 4f52bb2ffe Add option hmcl.update_source.override 2018-07-31 19:41:50 +08:00
yushijinhun dd45e1b3db Add JAR integrity check 2018-07-31 19:39:21 +08:00
yushijinhun e38bfdfc73 Refactor build.gradle 2018-07-31 15:48:29 +08:00
yushijinhun 73caebff00 Remove unnecessary saveConfig() call 2018-07-31 14:33:03 +08:00
yushijinhun e0608cdf3e Remove unused files 2018-07-31 14:32:42 +08:00
yushijinhun c815fa80e8 Prevent /build being created 2018-07-31 14:06:54 +08:00
yushijinhun e91e1cab47 Remove coreCompile/Runtime.extendsFrom 2018-07-31 14:04:36 +08:00
yushijinhun b791878651 Remove generateSources task 2018-07-31 14:02:26 +08:00
yushijinhun 8192ffec03 Remove duplicated HMCLauncher.exe 2018-07-31 13:59:48 +08:00
yushijinhun afa6eb3bad Refactor build.gradle 2018-07-31 13:46:31 +08:00
yushijinhun 6f7d22518e Remove pack.gz build 2018-07-31 13:34:17 +08:00
yushijinhun 2663312dfb Auto-rename after upgrade 2018-07-31 13:28:39 +08:00
yushijinhun 1332517a9f Rewrite upgrade feature 2018-07-30 23:32:01 +08:00
yushijinhun 13e227851f Refactor IconedItem 2018-07-29 21:10:04 +08:00
yushijinhun e1b6c18017 Remove HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING 2018-07-29 19:07:48 +08:00
yushijinhun a88c8ed85f Extract class Metadata 2018-07-29 19:03:49 +08:00
yushijinhun 25a90fc481 Remove upgrade feature 2018-07-29 18:42:31 +08:00
huangyuhui 1371206323 Pack with exe launcher 2018-07-29 16:34:15 +08:00
huanghongxun 129f6bb5cc Merge pull request #407 from yushijinhun/config-permission
重构 Config
2018-07-29 14:44:23 +08:00
yushijinhun 545c17d052 Print current directory & hmcl directory when bootstrap 2018-07-29 14:27:50 +08:00
yushijinhun add00ddba1 Use configured locale as soon as possible 2018-07-29 14:21:43 +08:00
yushijinhun d9aeb0cc2b Use SupportedLocale in Config 2018-07-29 14:15:32 +08:00
yushijinhun 49e70e7195 Add ConfigHolder.isNewlyCreated() 2018-07-29 14:02:32 +08:00
yushijinhun f8abebf110 Remove isFirstLaunch 2018-07-29 13:45:22 +08:00
yushijinhun 8e2dfc7341 Show error dialog when hmcl.json is not accessible 2018-07-29 13:40:59 +08:00
yushijinhun e650ffffce Initialize ConfigHolder & Settings manually 2018-07-29 13:40:59 +08:00
yushijinhun d549a300ab Replace Settings.INSTANCE with Settings.instance() 2018-07-29 13:40:59 +08:00
yushijinhun 82a62e73c1 Replace CONFIG with config() 2018-07-29 13:40:57 +08:00
yushijinhun 45c7e0c231 Remove Settings.save() 2018-07-29 13:39:32 +08:00
huangyuhui b98a4b11a3 Fix #406 2018-07-29 13:31:18 +08:00
huangyuhui 484a103f27 Fix always showing the welcome page when upgrading from old versions 2018-07-29 13:25:15 +08:00
huangyuhui e31120be21 Friendly prompt that it cannot download some libraries successfully. 2018-07-22 18:50:44 +08:00
huangyuhui 247f02d423 Friendly prompt to ServerResponseMalformedException 2018-07-22 18:22:28 +08:00
huangyuhui 3da0c1d8e3 Abandon Launch4j which will not find Java inst in PATH or JAVA_HOME 2018-07-22 18:15:39 +08:00
huangyuhui c830bbc703 reorder version in main page 2018-07-21 21:58:46 +08:00
huangyuhui 7b619fef1d Fix always using BMCLAPIDownloadProvider 2018-07-21 20:24:54 +08:00
huanghongxun bac265a077 Merge pull request #399 from yushijinhun/refactor-account
重构账户部分
2018-07-21 19:50:46 +08:00
huanghongxun 39df0f8d43 Merge branch 'javafx' into refactor-account 2018-07-21 19:50:16 +08:00
huangyuhui 6af7f916ee Add an option to disable common directory globally 2018-07-21 19:45:59 +08:00
yushijinhun ae64097edf Automatically select the newly added account 2018-07-21 12:34:12 +08:00
yushijinhun cb6d8a88f1 Fix the old account isn't replaced when adding a same account 2018-07-21 12:31:08 +08:00
huangyuhui 12838994ad Reconstruct MultiFileItem 2018-07-21 11:39:41 +08:00
yushijinhun d30758a2a8 Throw NPE when selected server is null 2018-07-21 09:57:47 +08:00
yushijinhun b820aad358 Use MappedObservableList in AuthlibInjectorServersPage 2018-07-20 20:26:27 +08:00
yushijinhun 170189c344 Refactor accounts 2018-07-20 20:26:27 +08:00
yushijinhun f9e9c9d38b Use LinkedHashMap for mapOf 2018-07-20 20:26:27 +08:00
yushijinhun 088af981df Turn properties in SettingsPage from local variables into fields 2018-07-20 20:26:27 +08:00
yushijinhun 357fef8672 Call initProxy() manually 2018-07-20 20:26:27 +08:00
yushijinhun c38848ea0e Rename fields in Accounts 2018-07-20 20:26:27 +08:00
yushijinhun be8cd13b08 Throw IAE when getAccountType() receives an unexpected param 2018-07-20 20:26:27 +08:00
yushijinhun 487816ab74 Use AccountFactory in cboType 2018-07-20 20:26:27 +08:00
yushijinhun bcd9f8240c Extract account factories to static fields 2018-07-20 20:26:27 +08:00
huangyuhui 1b62972f06 Fix 1.13 version parsing 2018-07-20 11:44:50 +08:00
huangyuhui 1117c3bfab Add missing data binding 2018-07-19 18:30:47 +08:00
huangyuhui 748d195db5 launch4j 2018-07-19 00:55:23 +08:00
huanghongxun 3a9017adb8 Merge pull request #396 from yushijinhun/fix
支持背景热替换
2018-07-18 23:05:56 +08:00
yushijinhun eff3186ab5 Background hot replacement 2018-07-18 22:51:02 +08:00
yushijinhun e5f5fe878e Use bindBidirectional for background type 2018-07-18 21:59:12 +08:00
yushijinhun a47f0f6ecf Use databind for theme subtitle 2018-07-18 21:47:30 +08:00
yushijinhun 9896165b68 Remove theme in Settings 2018-07-18 21:36:44 +08:00
huanghongxun 5e3adcee93 Merge pull request #395 from yushijinhun/fix
Fix ExportWizardProvider leaks proxy config
2018-07-18 20:42:42 +08:00
yushijinhun b252266a46 Fix ExportWizardProvider leaks proxy config 2018-07-18 20:40:18 +08:00
huanghongxun 1d5adea114 Merge pull request #394 from yushijinhun/refactor-settings
重构 Settings
2018-07-18 20:17:12 +08:00
yushijinhun a64a51c7df Refactor proxy UI 2018-07-18 20:07:13 +08:00
yushijinhun ec41c13b45 Update docs for tasks in org.jackhuang.hmcl.download.game 2018-07-18 20:05:54 +08:00
yushijinhun 486ca434ff Remove proxy HMCLDependencyManager 2018-07-18 20:05:54 +08:00
yushijinhun fe87a899c8 Remove proxy from AccountHelper 2018-07-18 20:05:54 +08:00
yushijinhun 06806bd383 Remove proxy from FileDownloadTask/GetTask/NetworkUtils 2018-07-18 20:05:54 +08:00
yushijinhun 2f6c7e1ce2 Remove proxy from AccountFactory 2018-07-18 20:05:54 +08:00
yushijinhun 141cda13f1 Add proxyProperty() 2018-07-18 20:05:54 +08:00
yushijinhun 3e8c8afcd1 Add get/set/property methods for Config 2018-07-18 20:05:16 +08:00
yushijinhun 8236f613e0 Use static import for ConfigHolder.CONFIG 2018-07-18 20:03:34 +08:00
yushijinhun ea6108dca7 Use bindBidirectional 2018-07-18 20:02:33 +08:00
yushijinhun 985a4f07e0 Use Authenticator to set proxy username/password 2018-07-18 20:02:28 +08:00
yushijinhun 5642c579a4 Add ProxyManager 2018-07-17 22:57:33 +08:00
yushijinhun 8e0559fb3b Remove proxyType 2018-07-17 22:56:00 +08:00
yushijinhun a6a1755fee Remove getRawConfig 2018-07-17 22:56:00 +08:00
yushijinhun 846f383f7b Remove backgroundImageTypeProperty 2018-07-17 22:56:00 +08:00
yushijinhun bbbe3726f2 Remove backgroundImageProperty 2018-07-17 22:56:00 +08:00
yushijinhun 6d83fd9903 Remove simple properties in Settings 2018-07-17 22:56:00 +08:00
yushijinhun fac62a8d24 Replace save() with observer pattern 2018-07-17 22:56:00 +08:00
yushijinhun b0d71a5511 Let Config implement Observable 2018-07-17 22:56:00 +08:00
huangyuhui 124ba34d48 Prevent appearance of the horizontal slide bar in install type page 2018-07-17 21:00:22 +08:00
huangyuhui 75270b8ad6 Fix wrong relativized path 2018-07-17 20:56:49 +08:00
yushijinhun 5cf6ef88a6 Let Account implement Observable 2018-07-17 17:04:27 +08:00
huangyuhui b5f72af513 Friendly hint of failing to rename game version 2018-07-16 20:39:21 +08:00
huangyuhui 1532893c5f Change current directory to run directory for launching scripts 2018-07-16 19:51:12 +08:00
huanghongxun a3fbc9595a Merge pull request #385 from yushijinhun/relative-path
添加设置项:若可能,游戏目录使用相对路径
2018-07-15 18:51:34 +08:00
yushijinhun f789343b6e Add game dir option: use relative path 2018-07-14 14:40:59 +08:00
yushijinhun ff74417352 [FileItem]Replace setProperty with bindBidirectional 2018-07-14 14:40:34 +08:00
huanghongxun a0ff843d0c Merge pull request #383 from yushijinhun/fix-382
Fix #382
2018-07-13 18:39:02 +08:00
yushijinhun da93cb0dbb Fix #382 2018-07-13 18:19:18 +08:00
huangyuhui 0de446c608 Update JFoenix 2018-07-11 22:33:29 +08:00
huangyuhui 03bccf16d4 Remove replacement of logs 2018-07-11 20:56:36 +08:00
huangyuhui 635ae8c11e Remove unused preset themes 2018-07-11 20:55:56 +08:00
huangyuhui dfd5a82112 Fix #365 by blocking all hacked Minecraft logs text as XML 2018-07-11 20:11:09 +08:00
huangyuhui 7dad8d452f Filter preview version of OptiFine 2018-07-11 13:08:08 +08:00
huangyuhui 07f8294876 Fixed mis-unbound data. GH-377 2018-07-11 12:46:02 +08:00
huanghongxun 7185cc410b Merge pull request #376 from yushijinhun/fix
Fix selected account is not loaded
2018-07-09 21:41:44 +08:00
yushijinhun 2d985af60d Fix selected account is not loaded 2018-07-09 21:34:43 +08:00
huanghongxun bee2e64482 Merge pull request #373 from yushijinhun/account-page
完善账户信息页的显示
2018-07-09 19:14:26 +08:00
huanghongxun d5dd43c0c1 Merge pull request #372 from yushijinhun/bmclapi
支持从 BMCLAPI 下载 authlib-injector
2018-07-09 19:14:00 +08:00
yushijinhun 09470eeb8a Use auto layout instead of manual sizing 2018-07-09 14:35:30 +08:00
yushijinhun 094249a6fa Set margin of fields in AccountPage 2018-07-09 14:31:41 +08:00
yushijinhun 6b5302907a [AccountPage]Display auth server url in tooltip of auth server name 2018-07-09 14:09:45 +08:00
yushijinhun adc5b08765 Fix account page overflow 2018-07-09 14:04:30 +08:00
yushijinhun 0150e2a8d7 Support downloading authlib-injector from BMCLAPI 2018-07-09 13:17:19 +08:00
huanghongxun 45eaa0c60f Merge pull request #371 from yushijinhun/fix
Refactor(WIP) & Fixes
2018-07-08 20:06:32 +08:00
yushijinhun 66ed683196 Move config load/save code to ConfigHolder(WIP) 2018-07-08 19:32:43 +08:00
yushijinhun aa4239721e Use NIO api to load/save config 2018-07-08 19:19:37 +08:00
yushijinhun 5b0b926cbd Move config serialization/deserialization to Config class 2018-07-08 19:17:53 +08:00
yushijinhun 929ce398eb AddAccountPane 使用数据绑定 & 登录时设置disable为true 2018-07-08 18:33:37 +08:00
yushijinhun b3fdbe021d 添加验证服务器界面回车代表下一步 2018-07-08 17:47:37 +08:00
huanghongxun 423bd68ce8 Merge pull request #369 from yushijinhun/fix
Fixes
2018-07-08 12:36:29 +08:00
yushijinhun 9bffc68c70 Do not reuse dialog 2018-07-08 12:20:38 +08:00
yushijinhun b7a5a60091 Improve English translations 2018-07-08 10:25:41 +08:00
yushijinhun 333b11c19f Fix uncaught JsonParseException, mentioned in #367 2018-07-08 10:11:14 +08:00
huanghongxun fbd4d1b772 Merge pull request #368 from yushijinhun/dnd
支持 DnD 方式添加验证服务器
2018-07-07 23:28:53 +08:00
yushijinhun 06a58514b3 Fix black ComboBox 2018-07-07 23:06:44 +08:00
yushijinhun 80815fc3a0 authlib-injector DnD support 2018-07-07 23:04:56 +08:00
huanghongxun b97c277a3f Merge pull request #364 from yushijinhun/refactor-ui
UI 重构 & bug 修复
2018-07-07 20:53:22 +08:00
yushijinhun 310d693e2b Remove some error cause inferences
Trampoline - High frequency of false positives.
java.lang.OutOfMemoryError - It's more likely to be a memory leak.
2018-07-07 20:51:28 +08:00
yushijinhun c1596f6a11 Replace Controllers.closeDialog with DialogCloseEvent 2018-07-07 20:50:54 +08:00
yushijinhun 76a04343d9 Use Controllers.dialog for AddAuthlibInjectorServerDialog 2018-07-07 20:30:48 +08:00
yushijinhun 25261c99f1 Remove 'class ' prefix in error messages 2018-07-07 20:30:48 +08:00
yushijinhun d0b994fa9d Move add authentication server dialog to a new file 2018-07-07 20:30:48 +08:00
yushijinhun f259601397 Each TransitionHandler instance uses its own empty pane
Using shared empty pane may cause some probleams
2018-07-07 20:30:47 +08:00
yushijinhun 87904109ca [TransitionHandler]Set mouseTransparent of the whole view
, fix bug introduced in 3fb5048f10
2018-07-07 20:30:47 +08:00
yushijinhun 785790ccc4 Move promptText of cboServers to FXML 2018-07-07 20:29:03 +08:00
yushijinhun 759921d00e Fix missing license headers 2018-07-07 20:28:59 +08:00
huangyuhui e9db183d54 Fix blank page of add authlib-injector server dialog 2018-07-07 20:08:40 +08:00
huangyuhui a853374a88 Support new MultiMC modpack 2018-07-07 00:48:48 +08:00
huangyuhui 3fb5048f10 Fix IllegalStateException in DownloadWizardProvider 2018-07-04 21:41:07 +08:00
huangyuhui 0dd2e758e8 Update READMD.md 2018-07-03 13:02:35 +08:00
huanghongxun d1a562452a Merge pull request #362 from yushijinhun/dst-x3
Add the prompt indicating the miss of DST Root CA X3.
2018-07-01 22:55:01 +08:00
yushijinhun 9205a6cbf4 Show warning when DST Root CA X3 is missing 2018-07-01 22:51:48 +08:00
yushijinhun 4c8961f39a Fix NPE when calling i18n during its initialization 2018-07-01 22:50:28 +08:00
huanghongxun 22bdc23a09 Merge pull request #361 from yushijinhun/i18n
Reconstruct I18N.
2018-07-01 22:11:23 +08:00
yushijinhun 764cd16e21 I18n for missing JavaFX error 2018-07-01 22:00:22 +08:00
yushijinhun 7583f4651d Remove I18nException 2018-07-01 21:59:16 +08:00
yushijinhun 13e931cda4 Fallback to use Locales.DEFAULT when Settings has not been initialized 2018-07-01 21:59:12 +08:00
yushijinhun 9743cf8351 Use static import for I18n.i18n 2018-07-01 21:27:30 +08:00
yushijinhun e5ee5702f8 Move i18n things to util.i18n package 2018-07-01 21:27:28 +08:00
huangyuhui 5bd5b25b77 Add debug message of system details 2018-07-01 19:18:55 +08:00
huangyuhui 55d59f3779 Fix not on FX thread 2018-07-01 19:02:32 +08:00
huangyuhui 7d2ea0b77e Add Tranditional Chinese translation 2018-07-01 13:37:09 +08:00
huangyuhui 1f30c299a5 Fix #360 2018-07-01 13:34:52 +08:00
huanghongxun c887d7e649 Merge pull request #358 from yushijinhun/authlib-injector
重构 authlib-injector 下载,并切换到新 API
2018-07-01 11:54:55 +08:00
yushijinhun 3fc7743a14 Add missing license header 2018-06-30 22:45:57 +08:00
yushijinhun 2a232f70db Refactor authlib-injector downloading, switch to new API 2018-06-30 22:45:57 +08:00
yushijinhun 6fae131287 Display (None) instead of empty string if no Yggdrasil server is available when adding account 2018-06-30 22:44:26 +08:00
yushijinhun 7ee7ef25f9 Refactor integrity check in FileDownloadTask, support multi algorithms 2018-06-30 22:43:57 +08:00
huangyuhui 2bd4bc1472 Add debug message in Task.run 2018-06-30 22:24:46 +08:00
huanghongxun aa170b647e Merge pull request #359 from yushijinhun/fixes
Fix bugs with yggdrasil authentication.
2018-06-30 22:12:06 +08:00
huangyuhui 4fa01216b1 Add debug message for tracing EmptyStackException and IllegalStateException 2018-06-30 22:09:35 +08:00
yushijinhun a82eb996a4 Add missing license header 2018-06-30 21:54:31 +08:00
yushijinhun 9315684ee3 Use CredentialExpiredException to indicate password login
instead of Non-ServerDisconnectException
2018-06-30 21:53:11 +08:00
huangyuhui 34f7774ce9 Fix NPE when the game launched and suddenly crashes 2018-06-30 21:36:57 +08:00
huangyuhui 75031a04cc Fix not on FX application thread 2018-06-30 21:28:41 +08:00
yushijinhun cfb0526cbb Fix potential NPE in YggdrasilAccount(selectedProfile is null) 2018-06-30 20:54:27 +08:00
yushijinhun 9fa56a9cd4 Regard token to be invalid only when remote throws ForbiddenOperationException 2018-06-30 18:52:37 +08:00
huangyuhui c4db9c9e12 Always show the advanced version setting pane as some people cannot find the options 2018-06-27 01:06:43 +08:00
huangyuhui 71272371e4 Fix #356 2018-06-26 21:55:29 +08:00
huangyuhui 91c6c4d233 Reduce size of welcome.png 2018-06-23 22:37:41 +08:00
huangyuhui 8a6653a926 Only show welcome page to Chinese 2018-06-23 22:25:39 +08:00
huangyuhui 76caf84e39 Welcome page 2018-06-23 22:15:23 +08:00
huangyuhui 0fee286b84 Use BMCLAPI as default ownload provider 2018-06-23 19:42:36 +08:00
huangyuhui 4af18a1575 Change build status icon in README.md 2018-06-23 13:03:28 +08:00
huangyuhui d9293f4da8 Display remote error message when RemoteAuthenticationException thrown 2018-06-21 19:31:16 +08:00
huangyuhui db29f97397 Update the link for crash reports 2018-06-17 18:22:22 +08:00
huangyuhui 573f41611d Force using Java 8 when launching Minecraft 1.13 or later 2018-06-17 18:13:47 +08:00
huanghongxun 28ed103ec0 Merge pull request #353 from yushijinhun/translations
完善 UI 提示消息
2018-06-17 17:45:59 +08:00
huanghongxun 5e37750f00 Merge pull request #352 from yushijinhun/refactor-update
添加 hmcl.version.override 选项用于调试更新功能
2018-06-17 17:40:07 +08:00
yushijinhun 5fe3cd9406 移除input.url.http错误提示 2018-06-17 17:28:12 +08:00
yushijinhun b00cc99408 BMCLAPI的链接使用https 2018-06-17 17:27:44 +08:00
yushijinhun 9a66c3bba9 完善翻译
中英文间补白、更正大小写、调整措辞
2018-06-17 17:25:22 +08:00
yushijinhun 546921b445 删除未被使用过的i18n条目 2018-06-17 16:40:22 +08:00
yushijinhun 8b698d3a4c 更新设置页面中依赖信息 2018-06-17 15:33:35 +08:00
yushijinhun c4608a949b 处理未捕获异常时立即进行log,防止异常被忽略
THROWABLE_SET改名为CAUGHT_EXCEPTIONS,使用并发安全集合
2018-06-17 15:04:31 +08:00
yushijinhun bba1c559a3 为连接authlib-injector服务器失败添加错误消息 2018-06-17 14:41:26 +08:00
yushijinhun bdee9ea72f 将获取验证服务器信息的代码移入AuthlibInjectorServer 2018-06-17 14:26:38 +08:00
yushijinhun 520923ba08 跳过更新时记录日志 2018-06-17 13:44:04 +08:00
yushijinhun 936a74597f 若版本号包含SNAPSHOT则也不更新
通过Gradle构建出来的JAR版本号为3.1.SNAPSHOT这样的
2018-06-17 13:40:09 +08:00
yushijinhun 97132745cc 增加hmcl.version.override选项用于覆写HMCL版本 2018-06-17 13:30:05 +08:00
yushijinhun 2bb30f65f3 在AppDataUpgrader中使用ReflectiveOperationException替代具体反射异常 2018-06-17 13:11:56 +08:00
huangyuhui ec04d55793 Fix transition 2018-06-17 12:05:39 +08:00
huangyuhui 88671db566 Use spinner instead of indeterminate progress bar 2018-06-17 12:05:25 +08:00
huanghongxun 538f501bc0 Merge pull request #351 from yushijinhun/refactor-yggdrasil
authlib-injector 部分重构
2018-06-17 10:34:47 +08:00
huangyuhui 3b75d41a2f 修改更新服务器连接 2018-06-17 10:34:13 +08:00
yushijinhun 110149d0e6 AuthlibInjectorAccountFactory的addtionalData改为AuthlibInjectorServer类型 2018-06-16 23:14:04 +08:00
yushijinhun 89b68781fa 修复添加验证服务器页面的错误信息不会被清除 2018-06-16 23:09:16 +08:00
yushijinhun 4cdc54b9c1 修复添加账号时未选中验证服务器会NPE 2018-06-16 23:06:21 +08:00
yushijinhun 7673595a43 AddAccountPane中服务器列表数据视图分离 2018-06-16 22:58:39 +08:00
yushijinhun 76a259c107 AuthlibInjectorAccount中存储AuthlibInjectorServer对象而不是url 2018-06-16 21:26:36 +08:00
yushijinhun 5f40d01fb4 在AuthlibInjectorAccountFactory中使用AuthlibInjectorServer而不是url 2018-06-16 21:26:36 +08:00
yushijinhun df29d47c0a workaround: 将Settings中的Config字段改为静态 2018-06-16 21:26:36 +08:00
yushijinhun 10d4084dde AuthlibInjectorServerInfo重命名为AuthlibInjectorServer
更正其它措辞:ip->url
2018-06-16 21:26:34 +08:00
yushijinhun ba8cdb1b42 移除AuthlibInjectorServersPage的spinner 2018-06-16 21:25:25 +08:00
yushijinhun 22732fee6a 存储AuthlibInjectorServerInfo对象而不是url字符串 2018-06-16 21:24:30 +08:00
yushijinhun bfbc4613b9 在添加authlib-injector服务器的过程中,传递AuthlibInjectorServerInfo而不是url 2018-06-16 20:33:48 +08:00
yushijinhun 92e5eecaf2 将Settings中DEFAULT_PROFILE, HOME_PROFILE字段移至Profiles 2018-06-16 20:30:14 +08:00
yushijinhun 593159f4a6 将DEFAULT_ICON移至Constants, 并使用懒加载
当类初始化时, JavaFX可能还未启动, 这将使图像加载失败
2018-06-16 20:10:10 +08:00
yushijinhun 53cad0e56a 在因无法读取而移除账户时记录日志 2018-06-16 20:10:10 +08:00
huangyuhui d61006dd67 Fix uncaught IOException 2018-06-13 23:40:16 +08:00
huangyuhui eb565285b2 Fix curse mod downloading 2018-06-10 20:05:36 +08:00
huangyuhui 630f164877 Continue load old version if the newer version cannot launch 2018-06-10 00:10:17 +08:00
huangyuhui 38c6bce2c0 Change log folder to ~/.hmcl 2018-06-09 17:37:43 +08:00
huanghongxun 034734ec66 Merge pull request #348 from yushijinhun/fxgson
使用 FxGson
2018-06-09 01:12:50 +08:00
yushijinhun d875eeb22a 将Config中属性替换为JavaFX Property 2018-06-09 00:32:33 +08:00
yushijinhun 61db6b8fb2 添加FxGson依赖 2018-06-09 00:28:23 +08:00
huanghongxun 945d4cf20d Merge pull request #347 from yushijinhun/merge-head
重构
2018-06-08 21:34:36 +08:00
yushijinhun 631337c451 YggdrasilAccount记录角色uuid而不是名称
注:旧版本无法读取此版本配置(移除了character)。
2018-06-08 21:13:33 +08:00
yushijinhun 4f51e377ae 移除canPlayOffline() 2018-06-08 21:09:35 +08:00
yushijinhun e71c06a0d0 Arguments.addXXX改为链式调用 2018-06-08 21:09:35 +08:00
yushijinhun 13aabde5d9 完善Account文档 2018-06-08 21:09:35 +08:00
huanghongxun e3f371f457 Merge pull request #344 from yushijinhun/javafx
修复YggdrasilAccount读取到了错误的character
2018-06-08 16:36:50 +08:00
yushijinhun 6ee99b5736 修复YggdrasilAccount读取到了错误的character
引入:https://github.com/huanghongxun/HMCL/commit/6c4231a6b242c2a11af9768e424593cc6cc14bc5#diff-839dd42e51cd641828b00c5302c02b12R50
2018-06-08 16:31:56 +08:00
huangyuhui 895188b25f Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-06-08 16:06:31 +08:00
huanghongxun 9fba923a51 Merge pull request #343 from yushijinhun/javafx
修复 defaultJavaPath 不能保存的问题
2018-06-08 16:06:01 +08:00
huangyuhui 37a39098f8 Try to fix VersionNotFoundException. Closes #298 2018-06-08 16:05:33 +08:00
yushijinhun 6fb548e6c3 修复defaultJavaPath不能保存的问题(未添加监听器) 2018-06-08 15:19:47 +08:00
huangyuhui 110b7cb06f Should fix 'Never get to here' 2018-06-08 13:55:38 +08:00
huangyuhui e9070a77c3 Fixed java.lang.IllegalStateException: stream has already been operated upon or closed 2018-06-08 13:40:12 +08:00
huangyuhui 475e6e5511 Remove redundant URLs that download providers provide. Mentioned in #242 2018-06-08 13:13:42 +08:00
huangyuhui eed4c7c11a Load skin when added new account 2018-06-08 12:58:01 +08:00
huangyuhui d986d7ac03 Apply spinner instead of intermediate progress bar 2018-06-08 12:47:46 +08:00
huangyuhui f640922a50 Optimize imports 2018-06-08 12:47:34 +08:00
huangyuhui 118a6cf0d1 Fasten version item construction 2018-06-08 12:45:44 +08:00
huanghongxun 72871e6d9a Merge pull request #342 from yushijinhun/refactor-skin
Yggdrasil 部分重构及 bug 修复
2018-06-08 10:41:46 +08:00
yushijinhun 61acbcec0d 移除GameProfile类的TypeAdapter 2018-06-07 22:28:17 +08:00
yushijinhun 06af79e2b4 移除对legcay账户的支持 2018-06-07 22:25:31 +08:00
yushijinhun 6782602141 重构YggdrasilService相关代码 2018-06-07 21:12:38 +08:00
yushijinhun 8df17ae38a 使用静态方法pair()替代new Pair<>() 2018-06-07 21:08:25 +08:00
huanghongxun 1645a93adf Merge pull request #341 from yushijinhun/cleanup
清理冗余代码
2018-06-07 12:41:51 +08:00
yushijinhun 6a04ab37d7 清理Hex 2018-06-07 12:17:58 +08:00
yushijinhun 27c9abd9ed 清理DigestUtils 2018-06-07 12:16:34 +08:00
yushijinhun a08be2e11d 移除Charsets类 2018-06-07 11:47:36 +08:00
huanghongxun 17615b1791 Merge pull request #340 from yushijinhun/javafx
修复 Java 选择相关 bug
2018-06-07 01:15:44 +08:00
yushijinhun d75c3b584a 修复加载JREs时可能的并发修改异常 2018-06-07 01:06:13 +08:00
huangyuhui 20f919b93e Merge branch 'javafx' of https://github.com/huanghongxun/HMCL into javafx 2018-06-07 00:53:56 +08:00
huangyuhui 20244fe5cc Fasten versions loading 2018-06-07 00:50:15 +08:00
yushijinhun 6a89f0a661 将当前Java版本包含在getJREs()中 2018-06-07 00:28:33 +08:00
huanghongxun 3a9efdb03e Merge pull request #339 from yushijinhun/multiple-java
Linux下自动检测Java + 允许多个相同版本的JRE
2018-06-06 23:57:24 +08:00
huangyuhui 2ae00db94b Removed MultiFileItem tooltips 2018-06-06 23:54:19 +08:00
huangyuhui a9261b759b Shutdown Now to prevent NPE when task executed after app closed 2018-06-06 23:52:23 +08:00
yushijinhun 6b02dc3cd5 允许多个相同版本的Java共存 2018-06-06 23:43:58 +08:00
yushijinhun afa37c0d2f 自动检测Linux下的Java 2018-06-06 23:01:27 +08:00
huangyuhui 2575f10497 Fixed #336 2018-06-06 19:45:39 +08:00
huanghongxun 27c4a68a13 Merge pull request #338 from yushijinhun/openjdk
对 OpenJDK 更好的支持
2018-06-06 14:02:14 +08:00
yushijinhun f09cb54894 当JavaFX缺失时弹窗提示 2018-06-06 13:52:23 +08:00
yushijinhun 5431cfef79 对OpenJDK上出现的错误同样进行上报 2018-06-06 13:31:53 +08:00
huanghongxun 35c0f3cddc Merge pull request #337 from yushijinhun/javafx
完善 authlib-injector 功能
2018-06-03 03:12:19 +08:00
yushijinhun efe22ca613 防止执行平移动画时组件溢出面板 2018-06-02 20:09:36 +08:00
yushijinhun 3408a8ffcf 更改措辞:添加认证服务器->管理认证服务器 2018-06-02 19:59:00 +08:00
yushijinhun 60a140efb3 修复删除验证服务器时可能出现ConcurrentModificationException
初始化块执行时字段并不一定都完成了初始化,应当避免使用。
利用ConcurrentHashMap的弱一致性防止迭代中删除账户造成ConcurrentModificationException。
2018-06-02 19:38:03 +08:00
yushijinhun bbe584bd91 修复无法显示未设置皮肤的Yggdrasil账户的皮肤的问题 2018-06-02 19:21:41 +08:00
yushijinhun 3866377a28 使用UUID而不是角色名来标识皮肤 2018-06-02 18:15:41 +08:00
yushijinhun ebc0b4b2b0 移除AccountHelper未使用的单例 2018-06-02 18:06:32 +08:00
huanghongxun d6c65a320d Merge pull request #334 from yushijinhun/java9
移除对Unsafe的使用
2018-05-29 22:47:35 +08:00
yushijinhun 2e46d76165 移除Lang.get(Map,Object,Class) 2018-05-27 12:40:45 +08:00
yushijinhun 23037da406 移除对Unsafe的使用 2018-05-27 12:13:27 +08:00
yushijinhun 617261c74d 不使用ReflectionHelper来安装Tooltip 2018-05-27 12:09:04 +08:00
yushijinhun 6c0d6ee0cf 通过非反射方法获取总物理内存 2018-05-27 11:04:39 +08:00
huanghongxun 663e1ef7ab Merge pull request #332 from xfl03/javafx
Fix #325 #296
2018-05-20 19:40:39 +08:00
Alexander Xia 1a47d355f0 Fix #325 #296 2018-05-20 19:12:12 +08:00
huangyuhui e407994103 Fixed #331 2018-05-17 16:46:16 +08:00
huangyuhui bda2149c02 Fix #326 2018-04-30 01:59:33 +08:00
huangyuhui b925423d61 Fixded #324 2018-04-26 22:15:27 +08:00
huangyuhui f3906a37bf Fixed #321 2018-04-26 13:14:14 +08:00
huangyuhui a1e115ea6b Ignore some exceptions 2018-03-09 16:54:28 +08:00
huangyuhui 03383510ef Disable accept button in AddAccountPage 2018-03-09 16:45:12 +08:00
huangyuhui e8cf9644eb Fix IllegalArgumentException: Invalid UUID String 2018-03-09 16:31:39 +08:00
huangyuhui dbfcf1facc Try to fix empty stack of StackContainerPane 2018-03-09 16:24:02 +08:00
huangyuhui 44aff94937 Remove error 2018-03-06 11:25:15 +08:00
huangyuhui fb1fc92b7c Fix optifine 2018-03-06 11:19:21 +08:00
huangyuhui 71cfa64b16 Fix StackContainerPane 2018-03-06 10:19:37 +08:00
huangyuhui b200105a85 Fix Liteloader installing of BMCLAPI 2018-03-06 10:13:48 +08:00
huanghongxun ce83f3c5e1 Merge pull request #297 from andylizi/switch-to-https
Switch to Https for most links.
2018-03-05 20:57:30 +08:00
huanghongxun 57d3275856 Merge branch 'javafx' into switch-to-https 2018-03-05 20:56:26 +08:00
huangyuhui eb1a109afc Fix IllegalArgumentException: Folder parameter must be a valid folder 2018-03-05 20:36:38 +08:00
huangyuhui a03e3fea12 Logs stopApplication stack trace 2018-03-05 20:23:23 +08:00
huangyuhui c60ae111ef Progress bar in account page 2018-03-05 20:07:30 +08:00
huangyuhui 5eb46395ae Close popup when deleted an account 2018-03-05 19:13:58 +08:00
huangyuhui db08b61cfa Fix #301 2018-03-05 19:02:13 +08:00
huangyuhui 41d313baff Fix #303 2018-03-04 23:35:42 +08:00
huangyuhui 58c3ed9254 Fixed #302 2018-03-04 23:33:08 +08:00
huangyuhui affe36a568 Fix update again 2018-03-04 23:29:27 +08:00
huangyuhui 9ba6cddde7 Fix update 2018-03-04 19:09:11 +08:00
huangyuhui 5656c9f351 update update_link.php 2018-03-04 16:54:35 +08:00
huangyuhui ecf24a746e Fix crashing when unable to save version setting 2018-03-04 15:33:54 +08:00
huangyuhui 5b5a58a867 Fixed not terminating the process when closed window 2018-03-03 22:56:42 +08:00
huangyuhui c6ab7878e7 Optimize code 2018-03-02 23:42:38 +08:00
huangyuhui 99e85eba37 Fix NPE in LauncherHelper.executor.onFinish: executor.getRunningTasks 2018-03-02 23:11:17 +08:00
huangyuhui 3bf92e6f12 Fix process not exited when pressed Alt+F4 2018-03-02 22:54:36 +08:00
huangyuhui ba359305f6 Hide refresh and add button in MainPage 2018-03-02 19:22:10 +08:00
huangyuhui f826eb224c AccountPage maintainence 2018-03-02 10:41:25 +08:00
huangyuhui a675f4cb68 Upgrade message 2018-03-02 00:56:16 +08:00
huangyuhui 5e1e2c6047 Remove accountsPage 2018-03-02 00:43:33 +08:00
huangyuhui eba0990538 Collect log when launcher crashed 2018-03-01 01:02:05 +08:00
huangyuhui 1bcef2bab0 Added event logs. Cloese #244 2018-03-01 00:27:07 +08:00
huangyuhui f83777e1d2 Terminate when exclaimation mark is in the path 2018-02-28 23:34:31 +08:00
huangyuhui 9fa48090ce Show indeterminate progress bar when loading versions in MainPage 2018-02-28 22:15:46 +08:00
huangyuhui 7c8ac4fee8 more precise warning in authlib injector add server page 2018-02-28 20:36:32 +08:00
andylizi 76c254ee98 Switch the download links to HTTPS 2018-02-28 17:23:49 +08:00
huangyuhui e75ad64483 Fixed IllegalStateException when clicked the empty space of list in InstallTypePage 2018-02-27 23:27:26 +08:00
huangyuhui 17d1af181d Profile page title changed to display name 2018-02-27 23:25:10 +08:00
huangyuhui a72d27a431 May fix VersionNotFoundException when building main page version items 2018-02-27 23:18:19 +08:00
huangyuhui 4e9fc57aa4 Fix NullPointerException when launcher launched two game at the same time 2018-02-27 23:12:53 +08:00
huangyuhui 494b27cc22 May fix RemoteVersion not found exception 2018-02-27 23:05:05 +08:00
huangyuhui 3606186975 Fix NullPointerException when selected an version item in download.VersionsPage 2018-02-27 20:05:05 +08:00
huangyuhui 41dbdfc016 Fix NullPointerException when refreshing versions again 2018-02-27 19:58:43 +08:00
huangyuhui 655e5ca7e0 Fixed missed encoding in Log4jHandler 2018-02-27 11:13:07 +08:00
huangyuhui 3067d323de Fixed NullPointerException when switched profile 2018-02-26 20:16:14 +08:00
huangyuhui 1404a080ad library download optimization 2018-02-26 19:56:06 +08:00
huangyuhui cbb2a1b575 Unique duplicated libraries in minecraft json file 2018-02-26 11:21:34 +08:00
huangyuhui 30b276ec1a Forge xz library supported 2018-02-26 11:14:53 +08:00
huangyuhui 13a24d5b00 try to fix RejectedExecutionException 2018-02-25 11:10:31 +08:00
huangyuhui 40360cf18a Fixed installing game 2018-02-25 10:59:48 +08:00
huangyuhui 139e2ee78d try fix ConcurrentModificationException 2018-02-25 10:48:07 +08:00
huangyuhui 3ff466462c Better theme selection UI 2018-02-24 23:46:20 +08:00
huanghongxun 9ff2442789 Foreground text fill auto detected 2018-02-23 17:40:32 +08:00
huanghongxun b11e4ab1db Fixed font in dialog accept and cancel button. Closes #291 2018-02-23 14:53:08 +08:00
huanghongxun a52cc8c093 Fixed crash when launching 1.7.10 forge version 2018-02-23 11:36:44 +08:00
huanghongxun 506c409303 log4jHandler maintainence 2018-02-23 11:22:20 +08:00
huanghongxun 53a8446f4a Curse CDN download provider 2018-02-23 09:54:57 +08:00
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
985 changed files with 46959 additions and 57298 deletions

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
# 公告
提交前请确保你的启动器版本是**最新的开发版**,可以在启动器设置中更换更新通道至开发版获取更新。
**如果你在阅读完本公告后仍然希望在此处发布 issue,请将公告内容删去,并按照下面的模板填入相关信息。**
我们发现,对于不经常检查邮箱的人来说,GitHub Issues 的反馈效率太低,时隔几个小时甚至一天的情况很多。为了改善反馈效率,并将积极为 HMCL 提供问题反馈和建议的人聚集起来,我们希望在这里反馈的人可以在 HMCL 3 用户 QQ 群:219177735 中反馈信息,**而不在 GitHub Issues 中反馈问题**。
如果你希望在 QQ 群中反馈问题,也请将下面的信息表填好直接发在群中,加快我们的沟通速度。
# 问题提交
*完整地填下面的问题提交表对我们很重要,这可以加快我们分析问题原因的速度。*
* 启动器版本:
* 操作系统:
* Java 版本:
* 错误截图(最好请将整个电脑屏幕的截图发上来):
* 游戏版本(如果是启动通过启动器自带的自动安装功能安装的游戏):
* 对游戏做的修改(是否自行通过安装器安装 Rift 等 API,以及 mod):
* 游戏崩溃报告(如果有):
* 启动器崩溃报告(如果有):
* 启动器日志文件(出现问题后点击启动器设置中的【导出启动器日志】按钮):
* 问题描述(如何触发问题):
+24 -5
View File
@@ -4,14 +4,33 @@
hs_err_pid*
.gradle
.nb-gradle
*.lck
*.1
*.2
*.log
.mine*
NVIDIA
*.json
*.hmd
# gradle build
/build/
/HMCL/build/
/HMCLaF/build/
/HMCLAPI/build/
/HMCLCore/build/
/HMCLCore/build/
# idea
.idea
/out/
/HMCL/out/
/HMCLCore/out/
# eclipse
/bin/
/HMCL/bin/
/HMCLCore/bin/
.classpath
.project
.settings
# netbeans
.nb-gradle
-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.
Binary file not shown.
Binary file not shown.
Executable → Regular
+124 -161
View File
@@ -1,197 +1,160 @@
/*
* 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/}.
*/
plugins {
id 'com.github.johnrengelman.shadow' version '4.0.0'
id 'application'
}
import java.nio.file.FileSystems
import java.security.KeyFactory
import java.security.MessageDigest
import java.security.Signature
import java.security.spec.PKCS8EncodedKeySpec
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.jar.Pack200
import java.util.zip.GZIPOutputStream
import java.security.MessageDigest
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.nio.file.Files
plugins {
id "edu.sc.seis.macAppBundle" version "2.1.8"
id "me.tatarka.retrolambda" version "3.7.0"
id 'edu.sc.seis.launch4j' version '2.4.2'
//id "org.jetbrains.kotlin.jvm" version "1.1.2-2"
import org.tukaani.xz.LZMA2Options
import org.tukaani.xz.XZOutputStream
def buildnumber = System.getenv("BUILD_NUMBER") ?: "SNAPSHOT"
if (System.getenv("BUILD_NUMBER") != null && System.getenv("BUILD_NUMBER_OFFSET") != null)
buildnumber = (Integer.parseInt(System.getenv("BUILD_NUMBER")) - Integer.parseInt(System.getenv("BUILD_NUMBER_OFFSET"))).toString()
def versionroot = System.getenv("VERSION_ROOT") ?: "3.2"
version = versionroot + '.' + buildnumber
mainClassName = 'org.jackhuang.hmcl.Main'
dependencies {
compile project(":HMCLCore")
compile rootProject.files("lib/JFoenix.jar")
}
if (!hasProperty('mainClass')) {
ext.mainClass = 'org.jackhuang.hmcl.Main'
def digest(String algorithm, byte[] bytes) {
return MessageDigest.getInstance(algorithm).digest(bytes)
}
def buildnumber = System.getenv("TRAVIS_BUILD_NUMBER")
if (buildnumber == null)
buildnumber = System.getenv("BUILD_NUMBER")
if (buildnumber == null)
buildnumber = "60"
def versionroot = System.getenv("VERSION_ROOT")
if (versionroot == null)
versionroot = "2.7.10"
String mavenGroupId = 'HMCL'
String mavenVersion = versionroot + '.' + buildnumber
String bundleName = "Hello Minecraft! Launcher"
group = mavenGroupId
version = mavenVersion
String mavenArtifactId = name
task generateSources(type: Sync) {
from 'src/main/java'
into "$buildDir/generated-src"
filter(org.apache.tools.ant.filters.ReplaceTokens, tokens: [
'HELLO_MINECRAFT_LAUNCHER_VERSION_FOR_GRADLE_REPLACING': mavenVersion
])
}
compileJava.setSource "$buildDir/generated-src"
compileJava.dependsOn generateSources
configurations {
coreCompile.extendsFrom compile
coreRuntime.extendsFrom runtime
def createChecksum(File file) {
def algorithm = "SHA-1"
def suffix = "sha1"
new File(file.parentFile, file.name + "." + suffix).text = digest(algorithm, file.bytes).encodeHex().toString() + "\n"
}
configure(install.repositories.mavenInstaller) {
pom.project {
groupId = mavenGroupId
artifactId = mavenArtifactId
version = mavenVersion
def attachSignature(File jar) {
def keyLocation = System.getenv("HMCL_SIGNATURE_KEY");
if (keyLocation == null)
return
def privatekey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(new File(keyLocation).bytes))
def signer = Signature.getInstance("SHA512withRSA")
signer.initSign(privatekey)
new ZipFile(jar).withCloseable { zip ->
zip.stream()
.sorted(Comparator.comparing { it.name })
.filter { it.name != "META-INF/hmcl_signature" }
.forEach {
signer.update(digest("SHA-512", it.name.getBytes("UTF-8")))
signer.update(digest("SHA-512", zip.getInputStream(it).bytes))
}
}
def signature = signer.sign()
FileSystems.newFileSystem(URI.create("jar:" + jar.toURI()), [:]).withCloseable { zipfs ->
Files.newOutputStream(zipfs.getPath("META-INF/hmcl_signature")).withCloseable { it << signature }
}
}
dependencies {
compile project(":HMCLaF")
compile project(":HMCLAPI")
compile project(":HMCLCore")
}
ext.packer = Pack200.newPacker()
packer.properties()["pack.effort"] = "9"
ext.unpacker = Pack200.newUnpacker()
retrolambda {
javaVersion = JavaVersion.VERSION_1_7
def repack(File file) {
def packed = new ByteArrayOutputStream()
new JarFile(file).withCloseable { packer.pack(it, packed) }
new JarOutputStream(file.newOutputStream()).withCloseable { unpacker.unpack(new ByteArrayInputStream(packed.toByteArray()), it) }
}
jar {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
manifest {
attributes 'Created-By' : 'Copyright(c) 2013-2017 huangyuhui.',
'Main-Class' : mainClass
attributes 'Created-By': 'Copyright(c) 2013-2018 huangyuhui.',
'Main-Class': mainClassName,
'Multi-Release': 'true',
'Implementation-Version': version
}
finalizedBy shadowJar
}
shadowJar {
classifier = null
exclude 'META-INF/maven/**'
exclude 'META-INF/NOTICE.txt'
exclude 'META-INF/LICENSE.txt'
dependencies {
exclude(dependency('org.jetbrains:annotations'))
}
doLast {
new File("build/signed").mkdirs()
ant.signjar(signedjar: archivePath, jar: archivePath,
keystore: "HMCL.keystore", storepass: "123456",
alias: "HMCL")
def messageDigest = MessageDigest.getInstance("SHA1")
archivePath.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
messageDigest.update(buf, 0, bytesRead);
}
def sha1Hex = new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0')
def fileEx = new File(project.buildDir, "libs/" + archivePath.getName() + ".sha1")
if (!fileEx.exists()) fileEx.createNewFile()
fileEx.append sha1Hex
repack(jar.archivePath)
attachSignature(jar.archivePath)
createChecksum(jar.archivePath)
}
}
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'
def createExecutable(String suffix, String header) {
def output = new File(jar.archivePath.parentFile, jar.archivePath.name[0..-4] + suffix)
output.bytes = new File(project.projectDir, header).bytes
output << jar.archivePath.bytes
createChecksum(output)
}
processResources {
from(sourceSets.main.resources.srcDirs) {
exclude 'icon.icns'
ext.convertToBSS = { String resource ->
exclude resource
doFirst {
def cssFile = new File(this.projectDir, "src/main/resources/" + resource)
def bssFile = new File(this.projectDir, "build/compiled-resources/" + resource[0..-4] + "bss")
bssFile.parentFile.mkdirs()
exec {
commandLine 'javapackager', '-createbss', '-outdir', bssFile.parent, '-srcfiles', cssFile.path
}
}
}
from "build/compiled-resources"
convertToBSS "assets/css/root.css"
convertToBSS "assets/css/blue.css"
}
task makePack(dependsOn: jar) {
ext.outputPath = new File(jar.archivePath.parentFile, jar.archivePath.name[0..-4] + "pack")
doLast {
outputPath.newOutputStream().withCloseable { out ->
new JarFile(jar.archivePath).withCloseable { jarFile -> packer.pack(jarFile, out) }
}
createChecksum(outputPath)
}
}
task makeExecutable(dependsOn: jar) doLast {
ext {
jar.classifier = ''
makeExecutableinjar = jar.archivePath
jar.classifier = ''
makeExecutableoutjar = jar.archivePath
jar.classifier = ''
}
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".exe")
def fos = new FileOutputStream(loc)
def is = new FileInputStream(new File(project.buildDir, '../HMCLauncher.exe'))
int read
def bytes = new byte[8192]
while((read = is.read(bytes)) != -1)
fos.write(bytes, 0, read);
is.close()
is = new FileInputStream(makeExecutableinjar)
while((read = is.read(bytes)) != -1)
fos.write(bytes, 0, read);
is.close()
fos.close()
task makePackXz(dependsOn: makePack) doLast {
def packXz = new File(makePack.outputPath.parentFile, makePack.outputPath.name + ".xz")
new XZOutputStream(packXz.newOutputStream(), new LZMA2Options(6)).withCloseable { it << makePack.outputPath.bytes }
createChecksum(packXz)
}
task makePackGZ(dependsOn: jar) doLast {
ext {
jar.classifier = ''
makeExecutableinjar = jar.archivePath
jar.classifier = ''
makeExecutableoutjar = jar.archivePath
jar.classifier = ''
}
def loc = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz")
def os = new GZIPOutputStream(new FileOutputStream(loc))
Pack200.newPacker().pack new JarFile(makeExecutableinjar), os
os.close()
def messageDigest = MessageDigest.getInstance("SHA1")
loc.eachByte 1024 * 1024, { byte[] buf, int bytesRead ->
messageDigest.update(buf, 0, bytesRead);
}
def sha1Hex = new BigInteger(1, messageDigest.digest()).toString(16).padLeft(40, '0')
def fileEx = new File(project.buildDir, "libs/" + makeExecutableoutjar.getName().substring(0, makeExecutableoutjar.getName().length()-4)+".pack.gz.sha1")
if (!fileEx.exists()) fileEx.createNewFile()
fileEx.append sha1Hex
task makePackGz(dependsOn: makePack) doLast {
def packGz = new File(makePack.outputPath.parentFile, makePack.outputPath.name + ".gz")
new GZIPOutputStream(packGz.newOutputStream()).withCloseable { it << makePack.outputPath.bytes }
createChecksum(packGz)
}
task macAppCompressed(type: Zip, dependsOn: createApp) doLast {
archiveName "HMCL-$mavenVersion-MacOSApp.zip"
include '**'
destinationDir file("$buildDir/libs/")
from "$buildDir/macApp"
task makeExecutables(dependsOn: jar) doLast {
createExecutable("exe", "src/main/resources/assets/HMCLauncher.exe")
}
macAppBundle {
mainClassName = mainClass
icon = "src/main/icon.icns"
javaProperties.put("apple.laf.useScreenMenuBar", "true")
}
build.dependsOn makeExecutable
build.dependsOn makePackGZ
build.dependsOn macAppCompressed
build.dependsOn makePackXz
build.dependsOn makePackGz
build.dependsOn makeExecutables
+2
View File
@@ -0,0 +1,2 @@
rootProject.name = 'HMCL'
Binary file not shown.
@@ -0,0 +1,144 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl;
import com.jfoenix.utils.JFXUtilities;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.stage.Stage;
import org.jackhuang.hmcl.setting.ConfigHolder;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Paths;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Launcher extends Application {
@Override
public void start(Stage primaryStage) {
Thread.currentThread().setUncaughtExceptionHandler(CRASH_REPORTER);
try {
try {
ConfigHolder.init();
} catch (IOException e) {
Main.showErrorAndExit(i18n("fatal.config_loading_failure", Paths.get("").toAbsolutePath().normalize()));
}
// runLater to ensure ConfigHolder.init() finished initialization
Platform.runLater(() -> {
// When launcher visibility is set to "hide and reopen" without Platform.implicitExit = false,
// Stage.show() cannot work again because JavaFX Toolkit have already shut down.
Platform.setImplicitExit(false);
Controllers.initialize(primaryStage);
primaryStage.setResizable(false);
primaryStage.setScene(Controllers.getScene());
UpdateChecker.init();
primaryStage.show();
});
} catch (Throwable e) {
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
}
}
public static void main(String[] args) {
Thread.setDefaultUncaughtExceptionHandler(CRASH_REPORTER);
try {
LOG.info("*** " + Metadata.TITLE + " ***");
LOG.info("Operating System: " + System.getProperty("os.name") + ' ' + OperatingSystem.SYSTEM_VERSION);
LOG.info("Java Version: " + System.getProperty("java.version") + ", " + System.getProperty("java.vendor"));
LOG.info("Java VM Version: " + System.getProperty("java.vm.name") + " (" + System.getProperty("java.vm.info") + "), " + System.getProperty("java.vm.vendor"));
LOG.info("Java Home: " + System.getProperty("java.home"));
LOG.info("Current Directory: " + Paths.get("").toAbsolutePath());
LOG.info("HMCL Directory: " + Metadata.HMCL_DIRECTORY);
LOG.info("Memory: " + Runtime.getRuntime().maxMemory() / 1024 / 1024 + "MB");
ManagementFactory.getMemoryPoolMXBeans().stream().filter(bean -> bean.getName().equals("Metaspace")).findAny()
.ifPresent(bean -> LOG.info("Metaspace: " + bean.getUsage().getUsed() / 1024 / 1024 + "MB"));
launch(args);
} catch (Throwable e) { // Fucking JavaFX will suppress the exception and will break our crash reporter.
CRASH_REPORTER.uncaughtException(Thread.currentThread(), e);
}
}
public static void stopApplication() {
LOG.info("Stopping application.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
JFXUtilities.runInFX(() -> {
if (Controllers.getStage() == null)
return;
Controllers.getStage().close();
Schedulers.shutdown();
Controllers.shutdown();
Platform.exit();
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
});
}
public static void stopWithoutPlatform() {
LOG.info("Stopping application without JavaFX Toolkit.\n" + StringUtils.getStackTrace(Thread.currentThread().getStackTrace()));
JFXUtilities.runInFX(() -> {
if (Controllers.getStage() == null)
return;
Controllers.getStage().close();
Schedulers.shutdown();
Controllers.shutdown();
Lang.executeDelayed(OperatingSystem::forceGC, TimeUnit.SECONDS, 5, true);
});
}
public static List<File> getCurrentJarFiles() {
List<File> result = new LinkedList<>();
if (Launcher.class.getClassLoader() instanceof URLClassLoader) {
URL[] urls = ((URLClassLoader) Launcher.class.getClassLoader()).getURLs();
for (URL u : urls)
try {
File f = new File(u.toURI());
if (f.isFile() && (f.getName().endsWith(".exe") || f.getName().endsWith(".jar")))
result.add(f);
} catch (URISyntaxException e) {
return null;
}
}
if (result.isEmpty())
return null;
else
return result;
}
public static final CrashReporter CRASH_REPORTER = new CrashReporter();
}
+86 -219
View File
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,239 +13,106 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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.net.Proxy;
import java.security.GeneralSecurityException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509TrustManager;
import javax.swing.ImageIcon;
import javax.swing.JOptionPane;
import javax.swing.RepaintManager;
import org.jackhuang.hmcl.api.HMCLApi;
import org.jackhuang.hmcl.api.HMCLog;
import org.jackhuang.hmcl.api.ILogger;
import org.jackhuang.hmcl.api.PluginManager;
import org.jackhuang.hmcl.api.VersionNumber;
import org.jackhuang.hmcl.core.MCUtils;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.ui.LogWindow;
import org.jackhuang.hmcl.ui.MainFrame;
import org.jackhuang.hmcl.util.CrashReporter;
import org.jackhuang.hmcl.util.DefaultPlugin;
import org.jackhuang.hmcl.util.ui.MessageBox;
import org.jackhuang.hmcl.util.StrUtils;
import org.jackhuang.hmcl.util.lang.SupportedLocales;
import org.jackhuang.hmcl.util.log.Configuration;
import org.jackhuang.hmcl.util.log.appender.ConsoleAppender;
import org.jackhuang.hmcl.util.log.layout.DefaultLayout;
import org.jackhuang.hmcl.util.ui.MyRepaintManager;
import org.jackhuang.hmcl.util.upgrade.IUpgrader;
import org.jackhuang.hmcl.laf.BeautyEyeLNFHelper;
import org.jackhuang.hmcl.upgrade.UpdateHandler;
import org.jackhuang.hmcl.util.Logging;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import javax.swing.*;
import java.io.File;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
*
* @author huangyuhui
*/
public final class Main {
private static final X509TrustManager XTM = new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {
public static void main(String[] args) {
System.setProperty("java.net.useSystemProxies", "true");
System.setProperty("http.agent", "HMCL/" + Metadata.VERSION);
System.setProperty("javafx.autoproxy.disable", "true");
checkJavaFX();
checkDirectoryPath();
// This environment check will take ~300ms
thread(() -> checkDSTRootCAX3(), "CA Certificate Check", true);
Logging.start(Metadata.HMCL_DIRECTORY.resolve("logs"));
if (UpdateHandler.processArguments(args)) {
return;
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
Launcher.main(args);
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
private static void checkDirectoryPath() {
String currentDirectory = new File("").getAbsolutePath();
if (currentDirectory.contains("!")) {
// No Chinese translation because both Swing and JavaFX cannot render Chinese character properly when exclamation mark exists in the path.
showErrorAndExit("Exclamation mark(!) is not allowed in the path where HMCL is in.\n"
+ "The path is " + currentDirectory);
}
};
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;
private static void checkJavaFX() {
try {
Class.forName("javafx.application.Application");
} catch (ClassNotFoundException e) {
showErrorAndExit(i18n("fatal.missing_javafx"));
}
}
private static void checkDSTRootCAX3() {
TrustManagerFactory tmf;
try {
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
} catch (NoSuchAlgorithmException | KeyStoreException e) {
LOG.log(Level.WARNING, "Failed to init TrustManagerFactory", e);
// don't know what to do here
return;
}
for (TrustManager tm : tmf.getTrustManagers()) {
if (tm instanceof X509TrustManager) {
for (X509Certificate cert : ((X509TrustManager) tm).getAcceptedIssuers()) {
if ("CN=DST Root CA X3, O=Digital Signature Trust Co.".equals((cert.getSubjectDN().getName()))) {
return;
}
}
}
}
showWarningAndContinue(i18n("fatal.missing_dst_root_ca_x3"));
}
/**
* Make the main window title.
*
* @return the MainWindow title.
* Indicates that a fatal error has occurred, and that the application cannot start.
*/
public static String makeTitle() {
return LAUNCHER_NAME + ' ' + LAUNCHER_VERSION;
static void showErrorAndExit(String message) {
System.err.println(message);
System.err.println("A fatal error has occurred, forcibly exiting.");
JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
public static String shortTitle() {
return "HMCL" + ' ' + LAUNCHER_VERSION;
/**
* Indicates that potential issues have been detected, and that the application may not function properly (but it can still run).
*/
static void showWarningAndContinue(String message) {
System.err.println(message);
System.err.println("Potential issues have been detected.");
JOptionPane.showMessageDialog(null, message, "Warning", JOptionPane.WARNING_MESSAGE);
}
public static final Logger LOGGER = Logger.getLogger(Main.class.getName());
public static void main(String[] args) throws IOException {
{
if (new File("").getAbsolutePath().contains("!")) {
MessageBox.show("HMCL cannot run at such path that contains '!'");
return;
}
try {
File file = new File("hmcl.log").getAbsoluteFile();
if (!file.exists() && !file.createNewFile())
LOGGER.log(Level.WARNING, "Failed to create log file {0}", file);
Configuration.DEFAULT.appenders.add(new ConsoleAppender("File", new DefaultLayout(), true, new FileOutputStream(file), true));
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, "Failed to add log appender File because an error occurred while creating or opening hmcl.log", ex);
}
org.jackhuang.hmcl.util.log.logger.Logger logger = new org.jackhuang.hmcl.util.log.logger.Logger("HMCL");
HMCLog.LOGGER = new ILogger() {
@Override
public void log(String msg) {
logger.info(msg);
}
@Override
public void warn(String msg) {
logger.warn(msg);
}
@Override
public void warn(String msg, Throwable t) {
logger.warn(msg, t);
}
@Override
public void err(String msg) {
logger.error(msg);
}
@Override
public void err(String msg, Throwable t) {
logger.error(msg, t);
}
};
HMCLApi.HMCL_VERSION = VersionNumber.asVersion(LAUNCHER_VERSION);
PluginManager.getPlugin(DefaultPlugin.class);
for (String s : args)
if (s.startsWith("--plugin=")) {
String c = s.substring("--plugin=".length());
try {
PluginManager.getPlugin(Class.forName(c));
} catch (ClassNotFoundException ex) {
HMCLog.warn("Class: " + c + " not found, please add your plugin jar to class path.", ex);
}
} else if (s.startsWith("--help")) {
System.out.println("HMCL command line help");
System.out.println("--noupdate: this arg will prevent HMCL from initializing the newest app version in %appdata%/.hmcl");
System.out.println("--plugin=<your plugin class>: this arg will allow a new plugin to be loaded, please keep your jar in system class path and this class extends IPlugin.");
return;
}
PluginManager.loadPlugins();
IUpgrader.NOW_UPGRADER.parseArguments(HMCLApi.HMCL_VERSION, Arrays.asList(args));
System.setProperty("awt.useSystemAAFontSettings", "on");
System.setProperty("swing.aatext", "true");
System.setProperty("sun.java2d.noddraw", "true");
System.setProperty("sun.java2d.dpiaware", "false");
System.setProperty("https.protocols", "SSLv3,TLSv1");
try {
SSLContext c = SSLContext.getInstance("SSL");
c.init(null, new X509TrustManager[] { XTM }, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(c.getSocketFactory());
} catch (GeneralSecurityException ignore) {
}
HttpsURLConnection.setDefaultHostnameVerifier(HNV);
Thread.setDefaultUncaughtExceptionHandler(new CrashReporter(true));
HMCLog.log("*** " + Main.makeTitle() + " ***");
String s = Settings.getInstance().getLocalization();
for (SupportedLocales sl : SupportedLocales.values())
if (sl.name().equals(s)) {
SupportedLocales.setNowLocale(sl);
Locale.setDefault(sl.self);
JOptionPane.setDefaultLocale(sl.self);
}
if (System.getProperty("java.vm.name").contains("Open")) // OpenJDK
MessageBox.showLocalized("ui.message.open_jdk");
try {
BeautyEyeLNFHelper.launchBeautyEyeLNF();
RepaintManager.setCurrentManager(new MyRepaintManager());
} catch (Exception ex) {
HMCLog.warn("Failed to set look and feel...", ex);
}
LogWindow.INSTANCE.clean();
Settings.UPDATE_CHECKER.upgrade.register(IUpgrader.NOW_UPGRADER);
Settings.UPDATE_CHECKER.process(false).reg(t -> Main.invokeUpdate()).execute();
if (Settings.getInstance().getProxyType() != Proxy.Type.DIRECT && StrUtils.isNotBlank(Settings.getInstance().getProxyHost())) {
switch (Settings.getInstance().getProxyType()) {
case DIRECT:
break;
case HTTP:
System.setProperty("http.proxyHost", Settings.getInstance().getProxyHost());
if (StrUtils.isNotBlank(Settings.getInstance().getProxyPort()))
System.setProperty("http.proxyPort", Settings.getInstance().getProxyPort());
HMCLog.log("Initialized HTTP proxy.");
break;
case SOCKS:
System.setProperty("socksProxyHost", Settings.getInstance().getProxyHost());
if (StrUtils.isNotBlank(Settings.getInstance().getProxyPort()))
System.setProperty("socksProxyPort", Settings.getInstance().getProxyPort());
HMCLog.log("Initialized Socks v4/v5 proxy.");
break;
}
if (StrUtils.isNotBlank(Settings.getInstance().getProxyUserName()) && StrUtils.isNotBlank(Settings.getInstance().getProxyPassword()))
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(Settings.getInstance().getProxyUserName(), Settings.getInstance().getProxyPassword().toCharArray());
}
});
}
MainFrame.showMainFrame();
}
}
public static void invokeUpdate() {
MainFrame.INSTANCE.invokeUpdate();
}
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;
}
}
}
@@ -0,0 +1,42 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl;
import java.nio.file.Path;
import org.jackhuang.hmcl.util.io.JarUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
/**
* Stores metadata about this application.
*/
public final class Metadata {
private Metadata() {}
public static final String VERSION = System.getProperty("hmcl.version.override", JarUtils.thisJar().flatMap(JarUtils::getImplementationVersion).orElse("@develop@"));
public static final String NAME = "HMCL";
public static final String TITLE = NAME + " " + VERSION;
public static final String UPDATE_URL = System.getProperty("hmcl.update_source.override", "https://hmcl.huangyuhui.net/api/update_link");
public static final String CONTACT_URL = "https://hmcl.huangyuhui.net/contact";
public static final String HELP_URL = "https://hmcl.huangyuhui.net/help";
public static final String PUBLISH_URL = "http://www.mcbbs.net/thread-142335-1-1.html";
public static final Path MINECRAFT_DIRECTORY = OperatingSystem.getWorkingDirectory("minecraft");
public static final Path HMCL_DIRECTORY = OperatingSystem.getWorkingDirectory("hmcl");
}
@@ -1,37 +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.api.event.config;
import org.jackhuang.hmcl.api.event.SimpleEvent;
import org.jackhuang.hmcl.setting.Profile;
/**
* This event gets fired when the selected profile changed.
* <br>
* This event is fired on the {@link org.jackhuang.hmcl.api.HMCLApi#EVENT_BUS}
* @param source {@link org.jackhuang.hmcl.setting.Settings}
* @param Profile the new profile.
* @author huangyuhui
*/
public class ProfileChangedEvent extends SimpleEvent<Profile> {
public ProfileChangedEvent(Object source, Profile value) {
super(source, value);
}
}
@@ -1,35 +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.api.event.config;
import java.util.EventObject;
/**
* This event gets fired when loading profiles.
* <br>
* This event is fired on the {@link org.jackhuang.hmcl.api.HMCLApi#EVENT_BUS}
* @param source {@link org.jackhuang.hmcl.setting.Settings}
* @author huangyuhui
*/
public class ProfileLoadingEvent extends EventObject {
public ProfileLoadingEvent(Object source) {
super(source);
}
}
@@ -0,0 +1,47 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jackhuang.hmcl.download.DefaultCacheRepository;
import java.nio.file.Paths;
public class HMCLCacheRepository extends DefaultCacheRepository {
private final StringProperty directory = new SimpleStringProperty();
public HMCLCacheRepository() {
directory.addListener((a, b, t) -> changeDirectory(Paths.get(t)));
}
public String getDirectory() {
return directory.get();
}
public StringProperty directoryProperty() {
return directory;
}
public void setDirectory(String directory) {
this.directory.set(directory);
}
public static final HMCLCacheRepository REPOSITORY = new HMCLCacheRepository();
}
@@ -0,0 +1,51 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.launch.DefaultLauncher;
import org.jackhuang.hmcl.launch.ProcessListener;
import java.util.Map;
/**
* @author huangyuhui
*/
public final class HMCLGameLauncher extends DefaultLauncher {
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options) {
this(repository, versionId, authInfo, options, null);
}
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener) {
this(repository, versionId, authInfo, options, listener, true);
}
public HMCLGameLauncher(GameRepository repository, String versionId, AuthInfo authInfo, LaunchOptions options, ProcessListener listener, boolean daemon) {
super(repository, versionId, authInfo, options, listener, daemon);
}
@Override
protected Map<String, String> getConfigurations() {
Map<String, String> res = super.getConfigurations();
res.put("${launcher_name}", Metadata.NAME);
res.put("${launcher_version}", Metadata.VERSION);
return res;
}
}
@@ -0,0 +1,228 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.logging.Level;
public class HMCLGameRepository extends DefaultGameRepository {
private final Profile profile;
private final Map<String, VersionSetting> versionSettings = new HashMap<>();
private final Set<String> beingModpackVersions = new HashSet<>();
public boolean checkedModpack = false, checkingModpack = false;
public HMCLGameRepository(Profile profile, File baseDirectory) {
super(baseDirectory);
this.profile = profile;
}
public Profile getProfile() {
return profile;
}
@Override
public File getRunDirectory(String id) {
if (beingModpackVersions.contains(id) || isModpack(id))
return getVersionRoot(id);
else {
VersionSetting vs = profile.getVersionSetting(id);
switch (vs.getGameDirType()) {
case VERSION_FOLDER: return getVersionRoot(id);
case ROOT_FOLDER: return super.getRunDirectory(id);
case CUSTOM: return new File(vs.getGameDir());
default: throw new Error();
}
}
}
@Override
protected void refreshVersionsImpl() {
versionSettings.clear();
super.refreshVersionsImpl();
versions.keySet().forEach(this::loadVersionSetting);
try {
File file = new File(getBaseDirectory(), "launcher_profiles.json");
if (!file.exists() && !versions.isEmpty())
FileUtils.writeText(file, PROFILE);
} catch (IOException ex) {
Logging.LOG.log(Level.WARNING, "Unable to create launcher_profiles.json, Forge/LiteLoader installer will not work.", ex);
}
}
public void changeDirectory(File newDirectory) {
setBaseDirectory(newDirectory);
refreshVersionsAsync().start();
}
private void clean(File directory) throws IOException {
FileUtils.deleteDirectory(new File(directory, "crash-reports"));
FileUtils.deleteDirectory(new File(directory, "logs"));
}
public void clean(String id) throws IOException {
clean(getBaseDirectory());
clean(getRunDirectory(id));
}
private File getVersionSettingFile(String id) {
return new File(getVersionRoot(id), "hmclversion.cfg");
}
private void loadVersionSetting(String id) {
File file = getVersionSettingFile(id);
if (file.exists())
try {
VersionSetting versionSetting = GSON.fromJson(FileUtils.readText(file), VersionSetting.class);
initVersionSetting(id, versionSetting);
} catch (Exception ex) {
// If [JsonParseException], [IOException] or [NullPointerException] happens, the json file is malformed and needed to be recreated.
initVersionSetting(id, new VersionSetting());
}
}
/**
* Create new version setting if version id has no version setting.
* @param id the version id.
* @return new version setting, null if given version does not exist.
*/
public VersionSetting createVersionSetting(String id) {
if (!hasVersion(id))
return null;
if (versionSettings.containsKey(id))
return getVersionSetting(id);
else
return initVersionSetting(id, new VersionSetting());
}
private VersionSetting initVersionSetting(String id, VersionSetting vs) {
vs.addPropertyChangedListener(a -> saveVersionSetting(id));
versionSettings.put(id, vs);
return vs;
}
/**
* Get the version setting for version id.
*
* @param id version id
*
* @return may return null if the id not exists
*/
public VersionSetting getVersionSetting(String id) {
if (!versionSettings.containsKey(id))
loadVersionSetting(id);
VersionSetting setting = versionSettings.get(id);
if (setting != null && isModpack(id))
setting.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
return setting;
}
public File getVersionIconFile(String id) {
return new File(getVersionRoot(id), "icon.png");
}
public Image getVersionIconImage(String id) {
if (id == null || !isLoaded())
return new Image("/assets/img/grass.png");
Version version = getVersion(id);
File iconFile = getVersionIconFile(id);
if (iconFile.exists())
return new Image("file:" + iconFile.getAbsolutePath());
else if ("net.minecraft.launchwrapper.Launch".equals(version.getMainClass()))
return new Image("/assets/img/furnace.png");
else if ("cpw.mods.modlauncher.Launcher".equals(version.getMainClass()))
return new Image("/assets/img/furnace.png");
else
return new Image("/assets/img/grass.png");
}
public boolean saveVersionSetting(String id) {
if (!versionSettings.containsKey(id))
return false;
File file = getVersionSettingFile(id);
if (!FileUtils.makeDirectory(file.getAbsoluteFile().getParentFile()))
return false;
try {
FileUtils.writeText(file, GSON.toJson(versionSettings.get(id)));
return true;
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Unable to save version setting of " + id, e);
return false;
}
}
/**
* Make version use self version settings instead of the global one.
* @param id the version id.
* @return specialized version setting, null if given version does not exist.
*/
public VersionSetting specializeVersionSetting(String id) {
VersionSetting vs = getVersionSetting(id);
if (vs == null)
vs = createVersionSetting(id);
if (vs == null)
return null;
vs.setUsesGlobal(false);
return vs;
}
public void globalizeVersionSetting(String id) {
VersionSetting vs = getVersionSetting(id);
if (vs != null)
vs.setUsesGlobal(true);
}
public boolean forbidsVersion(String id) {
return FORBIDDEN.contains(id);
}
@Override
public File getModpackConfiguration(String version) {
return new File(getVersionRoot(version), "modpack.cfg");
}
public void markVersionAsModpack(String id) {
beingModpackVersions.add(id);
}
public void undoMark(String id) {
beingModpackVersions.remove(id);
}
private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.create();
private static final HashSet<String> FORBIDDEN = new HashSet<>(Arrays.asList("modpack", "minecraftinstance", "manifest"));
private static final String PROFILE = "{\"selectedProfile\": \"(Default)\",\"profiles\": {\"(Default)\": {\"name\": \"(Default)\"}},\"clientToken\": \"88888888-8888-8888-8888-888888888888\"}";
}
@@ -0,0 +1,82 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.Zipper;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Export the game to a mod pack file.
*/
public class HMCLModpackExportTask extends Task<Void> {
private final DefaultGameRepository repository;
private final String version;
private final List<String> whitelist;
private final Modpack modpack;
private final File output;
/**
* @param output mod pack file.
* @param version to locate version.json
*/
public HMCLModpackExportTask(DefaultGameRepository repository, String version, List<String> whitelist, Modpack modpack, File output) {
this.repository = repository;
this.version = version;
this.whitelist = whitelist;
this.modpack = modpack;
this.output = output;
onDone().register(event -> {
if (event.isFailed()) output.delete();
});
}
@Override
public void execute() throws Exception {
ArrayList<String> blackList = new ArrayList<>(HMCLModpackManager.MODPACK_BLACK_LIST);
blackList.add(version + ".jar");
blackList.add(version + ".json");
Logging.LOG.info("Compressing game files without some files in blacklist, including files or directories: usernamecache.json, asm, logs, backups, versions, assets, usercache.json, libraries, crash-reports, launcher_profiles.json, NVIDIA, TCNodeTracker");
try (Zipper zip = new Zipper(output.toPath())) {
zip.putDirectory(repository.getRunDirectory(version).toPath(), "minecraft", path -> {
if (path.isEmpty())
return true;
for (String s : blackList)
if (path.equals(s))
return false;
for (String s : whitelist)
if (path.equals(s))
return true;
return false;
});
Version mv = repository.getResolvedVersion(version);
String gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version))
.orElseThrow(() -> new IllegalStateException("Cannot parse the version of " + version));
zip.putTextFile(JsonUtils.GSON.toJson(mv.setJar(gameVersion)), "minecraft/pack.json"); // Making "jar" to gameVersion is to be compatible with old HMCL.
zip.putTextFile(JsonUtils.GSON.toJson(modpack.setGameVersion(gameVersion)), "modpack.json"); // Newer HMCL only reads 'gameVersion' field.
}
}
}
@@ -0,0 +1,98 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.download.DependencyManager;
import org.jackhuang.hmcl.download.game.VersionJsonSaveTask;
import org.jackhuang.hmcl.mod.MinecraftInstanceTask;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.mod.ModpackInstallTask;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public final class HMCLModpackInstallTask extends Task<Void> {
private final File zipFile;
private final String name;
private final HMCLGameRepository repository;
private final Modpack modpack;
private final List<Task<?>> dependencies = new LinkedList<>();
private final List<Task<?>> dependents = new LinkedList<>();
public HMCLModpackInstallTask(Profile profile, File zipFile, Modpack modpack, String name) {
DependencyManager dependency = profile.getDependency();
repository = profile.getRepository();
this.zipFile = zipFile;
this.name = name;
this.modpack = modpack;
File run = repository.getRunDirectory(name);
File json = repository.getModpackConfiguration(name);
if (repository.hasVersion(name) && !json.exists())
throw new IllegalArgumentException("Version " + name + " already exists");
dependents.add(dependency.gameBuilder().name(name).gameVersion(modpack.getGameVersion()).buildAsync());
onDone().register(event -> {
if (event.isFailed()) repository.removeVersionFromDisk(name);
});
ModpackConfiguration<Modpack> config = null;
try {
if (json.exists()) {
config = JsonUtils.GSON.fromJson(FileUtils.readText(json), new TypeToken<ModpackConfiguration<Modpack>>() {
}.getType());
if (!MODPACK_TYPE.equals(config.getType()))
throw new IllegalArgumentException("Version " + name + " is not a HMCL modpack. Cannot update this version.");
}
} catch (JsonParseException | IOException ignore) {
}
dependents.add(new ModpackInstallTask<>(zipFile, run, modpack.getEncoding(), "/minecraft", it -> !"pack.json".equals(it), config));
}
@Override
public List<Task<?>> getDependencies() {
return dependencies;
}
@Override
public List<Task<?>> getDependents() {
return dependents;
}
@Override
public void execute() throws Exception {
String json = CompressingUtils.readTextZipEntry(zipFile, "minecraft/pack.json");
Version version = JsonUtils.GSON.fromJson(json, Version.class).setId(name).setJar(null);
dependencies.add(new VersionJsonSaveTask(repository, version));
dependencies.add(new MinecraftInstanceTask<>(zipFile, modpack.getEncoding(), "/minecraft", modpack, MODPACK_TYPE, repository.getModpackConfiguration(name)));
}
public static final String MODPACK_TYPE = "HMCL";
}
@@ -0,0 +1,97 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.mod.Modpack;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.List;
/**
* @author huangyuhui
*/
public final class HMCLModpackManager {
public static final List<String> MODPACK_BLACK_LIST = Lang.immutableListOf(
"usernamecache.json", "usercache.json", // Minecraft
"launcher_profiles.json", "launcher.pack.lzma", // Minecraft Launcher
"pack.json", "launcher.jar", "hmclmc.log", "cache", // HMCL
"manifest.json", "minecraftinstance.json", ".curseclient", // Curse
"minetweaker.log", // Mods
"jars", "logs", "versions", "assets", "libraries", "crash-reports", "NVIDIA", "AMD", "screenshots", "natives", "native", "$native", "server-resource-packs", // Minecraft
"downloads", // Curse
"asm", "backups", "TCNodeTracker", "CustomDISkins", "data" // Mods
);
public static final List<String> MODPACK_SUGGESTED_BLACK_LIST = Lang.immutableListOf(
"fonts", // BetterFonts
"saves", "servers.dat", "options.txt", // Minecraft
"blueprints" /* BuildCraft */,
"optionsof.txt" /* OptiFine */,
"journeymap" /* JourneyMap */,
"optionsshaders.txt",
"mods/VoxelMods");
public static ModAdviser.ModSuggestion suggestMod(String fileName, boolean isDirectory) {
if (match(MODPACK_BLACK_LIST, fileName, isDirectory))
return ModAdviser.ModSuggestion.HIDDEN;
if (match(MODPACK_SUGGESTED_BLACK_LIST, fileName, isDirectory))
return ModAdviser.ModSuggestion.NORMAL;
else
return ModAdviser.ModSuggestion.SUGGESTED;
}
private static boolean match(List<String> l, String fileName, boolean isDirectory) {
for (String s : l)
if (isDirectory) {
if (fileName.startsWith(s + "/"))
return true;
} else if (fileName.equals(s))
return true;
return false;
}
/**
* Read the manifest in a HMCL modpack.
*
* @param file a HMCL modpack file.
* @param encoding encoding of modpack zip file.
* @throws IOException if the file is not a valid zip file.
* @throws JsonParseException if the manifest.json is missing or malformed.
* @return the manifest of HMCL modpack.
*/
public static Modpack readHMCLModpackManifest(Path file, Charset encoding) throws IOException, JsonParseException {
String manifestJson = CompressingUtils.readTextZipEntry(file, "modpack.json", encoding);
Modpack manifest = JsonUtils.fromNonNullJson(manifestJson, Modpack.class).setEncoding(encoding);
String gameJson = CompressingUtils.readTextZipEntry(file, "minecraft/pack.json", encoding);
Version game = JsonUtils.fromNonNullJson(gameJson, Version.class);
if (game.getJar() == null)
if (StringUtils.isBlank(manifest.getVersion()))
throw new JsonParseException("Cannot recognize the game version of modpack " + file + ".");
else
return manifest.setManifest(HMCLModpackManifest.INSTANCE);
else
return manifest.setManifest(HMCLModpackManifest.INSTANCE).setGameVersion(game.getJar());
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,17 +13,12 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.api.func;
package org.jackhuang.hmcl.game;
import java.io.IOException;
public final class HMCLModpackManifest {
public static final HMCLModpackManifest INSTANCE = new HMCLModpackManifest();
/**
*
* @author huangyuhui
*/
public interface CallbackIO<T> {
void call(T t) throws IOException;
private HMCLModpackManifest() {}
}
@@ -0,0 +1,579 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import javafx.application.Platform;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CredentialExpiredException;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.download.MaintainTask;
import org.jackhuang.hmcl.download.game.LibraryDownloadException;
import org.jackhuang.hmcl.launch.*;
import org.jackhuang.hmcl.mod.CurseCompletionException;
import org.jackhuang.hmcl.mod.CurseCompletionTask;
import org.jackhuang.hmcl.mod.ModpackConfiguration;
import org.jackhuang.hmcl.setting.LauncherVisibility;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.*;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.DialogController;
import org.jackhuang.hmcl.ui.LogWindow;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.construct.TaskExecutorDialogPane;
import org.jackhuang.hmcl.util.Log4jLevel;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.Pair;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.function.ExceptionalSupplier;
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
import org.jackhuang.hmcl.util.platform.CommandBuilder;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.ManagedProcess;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class LauncherHelper {
private final Profile profile;
private final Account account;
private final String selectedVersion;
private File scriptFile;
private final VersionSetting setting;
private LauncherVisibility launcherVisibility;
private boolean showLogs;
public LauncherHelper(Profile profile, Account account, String selectedVersion) {
this.profile = Objects.requireNonNull(profile);
this.account = Objects.requireNonNull(account);
this.selectedVersion = Objects.requireNonNull(selectedVersion);
this.setting = profile.getVersionSetting(selectedVersion);
this.launcherVisibility = setting.getLauncherVisibility();
this.showLogs = setting.isShowLogs();
}
private final TaskExecutorDialogPane launchingStepsPane = new TaskExecutorDialogPane(it -> {});
public void setTestMode() {
launcherVisibility = LauncherVisibility.KEEP;
showLogs = true;
}
public void launch() {
Logging.LOG.info("Launching game version: " + selectedVersion);
GameRepository repository = profile.getRepository();
Version version = repository.getResolvedVersion(selectedVersion);
Platform.runLater(() -> {
try {
checkGameState(profile, setting, version, () -> {
Controllers.dialog(launchingStepsPane);
Schedulers.newThread().execute(this::launch0);
});
} catch (InterruptedException ignore) {
}
});
}
public void makeLaunchScript(File scriptFile) {
this.scriptFile = Objects.requireNonNull(scriptFile);
launch();
}
private void launch0() {
HMCLGameRepository repository = profile.getRepository();
DefaultDependencyManager dependencyManager = profile.getDependency();
Version version = MaintainTask.maintain(repository.getResolvedVersion(selectedVersion));
Optional<String> gameVersion = GameVersion.minecraftVersion(repository.getVersionJar(version));
TaskExecutor executor = Task.runAsync(Schedulers.javafx(), () -> emitStatus(LoadingState.DEPENDENCIES))
.thenCompose(() -> {
if (setting.isNotCheckGame())
return null;
else
return dependencyManager.checkGameCompletionAsync(version);
})
.thenRun(Schedulers.javafx(), () -> emitStatus(LoadingState.MODS))
.thenCompose(() -> {
try {
ModpackConfiguration<?> configuration = ModpackHelper.readModpackConfiguration(repository.getModpackConfiguration(selectedVersion));
if ("Curse".equals(configuration.getType()))
return new CurseCompletionTask(dependencyManager, selectedVersion);
else
return null;
} catch (IOException e) {
return null;
}
})
.thenRun(Schedulers.javafx(), () -> emitStatus(LoadingState.LOGGING_IN))
.thenSupply(i18n("account.methods"), () -> {
try {
return account.logIn();
} catch (CredentialExpiredException e) {
LOG.info("Credential has expired: " + e);
return DialogController.logIn(account);
} catch (AuthenticationException e) {
LOG.warning("Authentication failed, try playing offline: " + e);
return account.playOffline().orElseThrow(() -> e);
}
})
.thenApply(Schedulers.javafx(), authInfo -> {
emitStatus(LoadingState.LAUNCHING);
return authInfo;
})
.thenApply(authInfo -> new HMCLGameLauncher(
repository,
selectedVersion,
authInfo,
setting.toLaunchOptions(profile.getGameDir()),
launcherVisibility == LauncherVisibility.CLOSE
? null // Unnecessary to start listening to game process output when close launcher immediately after game launched.
: new HMCLProcessListener(authInfo, gameVersion.isPresent())
))
.thenCompose(launcher -> { // launcher is prev task's result
if (scriptFile == null) {
return new LaunchTask<>(launcher::launch).setName(i18n("version.launch"));
} else {
return new LaunchTask<ManagedProcess>(() -> {
launcher.makeLaunchScript(scriptFile);
return null;
}).setName(i18n("version.launch_script"));
}
})
.thenAccept(process -> { // process is LaunchTask's result
if (scriptFile == null) {
PROCESSES.add(process);
if (launcherVisibility == LauncherVisibility.CLOSE)
Launcher.stopApplication();
else
launchingStepsPane.setCancel(it -> {
process.stop();
it.fireEvent(new DialogCloseEvent());
});
} else {
Platform.runLater(() -> {
launchingStepsPane.fireEvent(new DialogCloseEvent());
Controllers.dialog(i18n("version.launch_script.success", scriptFile.getAbsolutePath()));
});
}
})
.executor();
launchingStepsPane.setExecutor(executor, false);
executor.addTaskListener(new TaskListener() {
final AtomicInteger finished = new AtomicInteger(0);
@Override
public void onFinished(Task<?> task) {
finished.incrementAndGet();
int runningTasks = executor.getRunningTasks();
Platform.runLater(() -> launchingStepsPane.setProgress(1.0 * finished.get() / runningTasks));
}
@Override
public void onStop(boolean success, TaskExecutor executor) {
if (!success && !Controllers.isStopped()) {
Platform.runLater(() -> {
// Check if the application has stopped
// because onStop will be invoked if tasks fail when the executor service shut down.
if (!Controllers.isStopped()) {
launchingStepsPane.fireEvent(new DialogCloseEvent());
Exception ex = executor.getException();
if (ex != null) {
String message;
if (ex instanceof CurseCompletionException) {
if (ex.getCause() instanceof FileNotFoundException)
message = i18n("modpack.type.curse.not_found");
else
message = i18n("modpack.type.curse.error");
} else if (ex instanceof PermissionException) {
message = i18n("launch.failed.executable_permission");
} else if (ex instanceof ProcessCreationException) {
message = i18n("launch.failed.creating_process") + ex.getLocalizedMessage();
} else if (ex instanceof NotDecompressingNativesException) {
message = i18n("launch.failed.decompressing_natives") + ex.getLocalizedMessage();
} else if (ex instanceof LibraryDownloadException) {
message = i18n("launch.failed.download_library", ((LibraryDownloadException) ex).getLibrary().getName()) + "\n" + StringUtils.getStackTrace(ex.getCause());
} else {
message = StringUtils.getStackTrace(ex);
}
Controllers.dialog(message,
scriptFile == null ? i18n("launch.failed") : i18n("version.launch_script.failed"),
MessageType.ERROR);
}
}
});
}
launchingStepsPane.setExecutor(null);
}
});
executor.start();
}
private static void checkGameState(Profile profile, VersionSetting setting, Version version, Runnable onAccept) throws InterruptedException {
if (setting.isNotCheckJVM()) {
onAccept.run();
return;
}
boolean flag = false;
boolean java8required = false;
boolean newJavaRequired = false;
// Without onAccept called, the launching operation will be terminated.
VersionNumber gameVersion = VersionNumber.asVersion(GameVersion.minecraftVersion(profile.getRepository().getVersionJar(version)).orElse("Unknown"));
JavaVersion java = setting.getJavaVersion();
if (java == null) {
Controllers.dialog(i18n("launch.wrong_javadir"), i18n("message.warning"), MessageType.WARNING, onAccept);
setting.setJava(null);
setting.setDefaultJavaPath(null);
java = JavaVersion.fromCurrentEnvironment();
flag = true;
}
// Game later than 1.7.2 accepts Java 8.
if (!flag && java.getParsedVersion() < JavaVersion.JAVA_8 && gameVersion.compareTo(VersionNumber.asVersion("1.7.2")) > 0) {
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
.filter(javaVersion -> javaVersion.getParsedVersion() >= JavaVersion.JAVA_8)
.max(Comparator.comparing(JavaVersion::getVersionNumber));
if (java8.isPresent()) {
newJavaRequired = true;
setting.setJavaVersion(java8.get());
} else {
if (gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0) {
// Minecraft 1.13 and later versions only support Java 8 or later.
// Terminate launching operation.
Controllers.dialog(i18n("launch.advice.java8_1_13"), i18n("message.error"), MessageType.ERROR, null);
} else {
// Most mods require Java 8 or later version.
Controllers.dialog(i18n("launch.advice.newer_java"), i18n("message.warning"), MessageType.WARNING, onAccept);
}
flag = true;
}
}
// LaunchWrapper 1.12 will crash because of assuming the system class loader is an instance of URLClassLoader.
if (!flag && java.getParsedVersion() >= JavaVersion.JAVA_9
&& version.getMainClass().contains("launchwrapper")
&& version.getLibraries().stream()
.filter(library -> "launchwrapper".equals(library.getArtifactId()))
.anyMatch(library -> VersionNumber.asVersion(library.getVersion()).compareTo(VersionNumber.asVersion("1.13")) < 0)) {
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream().filter(javaVersion -> javaVersion.getParsedVersion() == JavaVersion.JAVA_8).findAny();
if (java8.isPresent()) {
java8required = true;
setting.setJavaVersion(java8.get());
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.corrected"), i18n("message.info"), MessageType.INFORMATION, onAccept);
flag = true;
} else {
Controllers.dialog(i18n("launch.advice.java9") + "\n" + i18n("launch.advice.uncorrected"), i18n("message.error"), MessageType.ERROR, null);
flag = true;
}
}
// Minecraft 1.13 may crash when generating world on Java 8 earlier than 1.8.0_51
VersionNumber JAVA_8 = VersionNumber.asVersion("1.8.0_51");
if (!flag && gameVersion.compareTo(VersionNumber.asVersion("1.13")) >= 0 && java.getParsedVersion() == JavaVersion.JAVA_8 && java.getVersionNumber().compareTo(JAVA_8) < 0) {
Optional<JavaVersion> java8 = JavaVersion.getJavas().stream()
.filter(javaVersion -> javaVersion.getVersionNumber().compareTo(JAVA_8) >= 0)
.max(Comparator.comparing(JavaVersion::getVersionNumber));
if (java8.isPresent()) {
newJavaRequired = true;
setting.setJavaVersion(java8.get());
} else {
Controllers.dialog(i18n("launch.advice.java8_51_1_13"), i18n("message.warning"), MessageType.WARNING, onAccept);
flag = true;
}
}
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
org.jackhuang.hmcl.util.platform.Platform.IS_64_BIT) {
final JavaVersion java32 = java;
// First find if same java version but whose platform is 64-bit installed.
Optional<JavaVersion> java64 = JavaVersion.getJavas().stream()
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
.filter(javaVersion -> javaVersion.getParsedVersion() == java32.getParsedVersion())
.max(Comparator.comparing(JavaVersion::getVersionNumber));
if (!java64.isPresent()) {
final boolean java8requiredFinal = java8required, newJavaRequiredFinal = newJavaRequired;
// Then find if other java version which satisfies requirements installed.
java64 = JavaVersion.getJavas().stream()
.filter(javaVersion -> javaVersion.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.PLATFORM)
.filter(javaVersion -> {
if (java8requiredFinal) return javaVersion.getParsedVersion() == JavaVersion.JAVA_8;
if (newJavaRequiredFinal) return javaVersion.getParsedVersion() >= JavaVersion.JAVA_8;
return true;
})
.max(Comparator.comparing(JavaVersion::getVersionNumber));
}
if (java64.isPresent()) {
setting.setJavaVersion(java64.get());
} else {
Controllers.dialog(i18n("launch.advice.different_platform"), i18n("message.error"), MessageType.ERROR, onAccept);
flag = true;
}
}
// 32-bit JVM cannot make use of too much memory.
if (!flag && java.getPlatform() == org.jackhuang.hmcl.util.platform.Platform.BIT_32 &&
setting.getMaxMemory() > 1.5 * 1024) {
// 1.5 * 1024 is an inaccurate number.
// Actual memory limit depends on operating system and memory.
Controllers.dialog(i18n("launch.advice.too_large_memory_for_32bit"), i18n("message.error"), MessageType.ERROR, onAccept);
flag = true;
}
// Cannot allocate too much memory exceeding free space.
if (!flag && OperatingSystem.TOTAL_MEMORY > 0 && OperatingSystem.TOTAL_MEMORY < setting.getMaxMemory()) {
Controllers.dialog(i18n("launch.advice.not_enough_space", OperatingSystem.TOTAL_MEMORY), i18n("message.error"), MessageType.ERROR, onAccept);
flag = true;
}
// Forge 2760~2773 will crash game with LiteLoader.
if (!flag) {
boolean hasForge2760 = version.getLibraries().stream().filter(it -> it.is("net.minecraftforge", "forge"))
.anyMatch(it ->
VersionNumber.VERSION_COMPARATOR.compare("1.12.2-14.23.5.2760", it.getVersion()) <= 0 &&
VersionNumber.VERSION_COMPARATOR.compare(it.getVersion(), "1.12.2-14.23.5.2773") < 0);
boolean hasLiteLoader = version.getLibraries().stream().anyMatch(it -> it.is("com.mumfrey", "liteloader"));
if (hasForge2760 && hasLiteLoader && gameVersion.compareTo(VersionNumber.asVersion("1.12.2")) == 0) {
Controllers.dialog(i18n("launch.advice.forge2760_liteloader"), i18n("message.error"), MessageType.ERROR, onAccept);
flag = true;
}
}
if (!flag)
onAccept.run();
}
public void emitStatus(LoadingState state) {
if (state == LoadingState.DONE) {
launchingStepsPane.fireEvent(new DialogCloseEvent());
}
launchingStepsPane.setTitle(state.getLocalizedMessage());
launchingStepsPane.setSubtitle((state.ordinal() + 1) + " / " + LoadingState.values().length);
}
private void checkExit() {
switch (launcherVisibility) {
case HIDE_AND_REOPEN:
Platform.runLater(Controllers.getStage()::show);
break;
case KEEP:
// No operations here
break;
case CLOSE:
throw new Error("Never get to here");
case HIDE:
Platform.runLater(() -> {
// Shut down the platform when user closed log window.
Platform.setImplicitExit(true);
// If we use Launcher.stop(), log window will be halt immediately.
Launcher.stopWithoutPlatform();
});
break;
}
}
private static class LaunchTask<T> extends Task<T> {
private final ExceptionalSupplier<T, Exception> supplier;
public LaunchTask(ExceptionalSupplier<T, Exception> supplier) {
this.supplier = supplier;
}
@Override
public void execute() throws Exception {
setResult(supplier.get());
}
}
/**
* The managed process listener.
* Guarantee that one [JavaProcess], one [HMCLProcessListener].
* Because every time we launched a game, we generates a new [HMCLProcessListener]
*/
class HMCLProcessListener implements ProcessListener {
private final Map<String, String> forbiddenTokens;
private ManagedProcess process;
private boolean lwjgl;
private LogWindow logWindow;
private final boolean detectWindow;
private final LinkedList<Pair<String, Log4jLevel>> logs;
private final CountDownLatch latch = new CountDownLatch(1);
public HMCLProcessListener(AuthInfo authInfo, boolean detectWindow) {
this.detectWindow = detectWindow;
if (authInfo == null)
forbiddenTokens = Collections.emptyMap();
else
forbiddenTokens = mapOf(
pair(authInfo.getAccessToken(), "<access token>"),
pair(UUIDTypeAdapter.fromUUID(authInfo.getUUID()), "<uuid>"),
pair(authInfo.getUsername(), "<player>")
);
logs = new LinkedList<>();
}
@Override
public void setProcess(ManagedProcess process) {
this.process = process;
if (showLogs)
Platform.runLater(() -> {
logWindow = new LogWindow();
logWindow.show();
latch.countDown();
});
}
private void finishLaunch() {
switch (launcherVisibility) {
case HIDE_AND_REOPEN:
Platform.runLater(() -> {
// If application was stopped and execution services did not finish termination,
// these codes will be executed.
if (Controllers.getStage() != null) {
Controllers.getStage().hide();
emitStatus(LoadingState.DONE);
}
});
break;
case CLOSE:
// Never come to here.
break;
case KEEP:
Platform.runLater(() -> {
emitStatus(LoadingState.DONE);
});
break;
case HIDE:
Platform.runLater(() -> {
// If application was stopped and execution services did not finish termination,
// these codes will be executed.
if (Controllers.getStage() != null) {
Controllers.getStage().close();
emitStatus(LoadingState.DONE);
}
});
break;
}
}
@Override
public synchronized void onLog(String log, Log4jLevel level) {
String newLog = log;
for (Map.Entry<String, String> entry : forbiddenTokens.entrySet())
newLog = newLog.replace(entry.getKey(), entry.getValue());
if (level.lessOrEqual(Log4jLevel.ERROR))
System.err.print(log);
else
System.out.print(log);
logs.add(pair(log, level));
if (logs.size() > config().getLogLines())
logs.removeFirst();
if (showLogs) {
try {
latch.await();
logWindow.waitForLoaded();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
Platform.runLater(() -> logWindow.logLine(log, level));
}
if (!lwjgl && (log.contains("LWJGL Version: ") || !detectWindow)) {
lwjgl = true;
finishLaunch();
}
}
@Override
public void onExit(int exitCode, ExitType exitType) {
if (exitType == ExitType.INTERRUPTED)
return;
// Game crashed before opening the game window.
if (!lwjgl) finishLaunch();
if (exitType != ExitType.NORMAL && logWindow == null)
Platform.runLater(() -> {
logWindow = new LogWindow();
switch (exitType) {
case JVM_ERROR:
logWindow.setTitle(i18n("launch.failed.cannot_create_jvm"));
break;
case APPLICATION_ERROR:
logWindow.setTitle(i18n("launch.failed.exited_abnormally"));
break;
}
logWindow.show();
logWindow.onDone.register(() -> {
logWindow.logLine("Command: " + new CommandBuilder().addAll(process.getCommands()).toString(), Log4jLevel.INFO);
for (Map.Entry<String, Log4jLevel> entry : logs)
logWindow.logLine(entry.getKey(), entry.getValue());
});
});
checkExit();
}
}
public static final Queue<ManagedProcess> PROCESSES = new ConcurrentLinkedQueue<>();
public static void stopManagedProcesses() {
while (!PROCESSES.isEmpty())
Optional.ofNullable(PROCESSES.poll()).ifPresent(ManagedProcess::stop);
}
}
@@ -0,0 +1,38 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public enum LoadingState {
DEPENDENCIES("launch.state.dependencies"),
MODS("launch.state.modpack"),
LOGGING_IN("launch.state.logging_in"),
LAUNCHING("launch.state.waiting_launching"),
DONE("launch.state.done");
private final String key;
LoadingState(String key) {
this.key = key;
}
public String getLocalizedMessage() {
return i18n(key);
}
}
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -13,22 +13,19 @@
* 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/}.
* along with this program. If not, see <https://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,186 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import org.jackhuang.hmcl.mod.*;
import org.jackhuang.hmcl.setting.EnumGameDirectory;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.function.ExceptionalConsumer;
import org.jackhuang.hmcl.util.function.ExceptionalRunnable;
import org.jackhuang.hmcl.util.gson.JsonUtils;
import org.jackhuang.hmcl.util.io.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.util.Optional;
public final class ModpackHelper {
private ModpackHelper() {}
public static Modpack readModpackManifest(Path file, Charset charset) throws UnsupportedModpackException {
try {
return CurseManifest.readCurseForgeModpackManifest(file, charset);
} catch (Exception e) {
// ignore it, not a valid CurseForge modpack.
}
try {
return HMCLModpackManager.readHMCLModpackManifest(file, charset);
} catch (Exception e) {
// ignore it, not a valid HMCL modpack.
}
try {
return MultiMCInstanceConfiguration.readMultiMCModpackManifest(file, charset);
} catch (Exception e) {
// ignore it, not a valid MultiMC modpack.
}
throw new UnsupportedModpackException(file.toString());
}
public static ModpackConfiguration<?> readModpackConfiguration(File file) throws IOException {
if (!file.exists())
throw new FileNotFoundException(file.getPath());
else
try {
return JsonUtils.GSON.fromJson(FileUtils.readText(file), new TypeToken<ModpackConfiguration<?>>() {
}.getType());
} catch (JsonParseException e) {
throw new IOException("Malformed modpack configuration");
}
}
private static String getManifestType(Object manifest) throws UnsupportedModpackException {
if (manifest instanceof HMCLModpackManifest)
return HMCLModpackInstallTask.MODPACK_TYPE;
else if (manifest instanceof MultiMCInstanceConfiguration)
return MultiMCModpackInstallTask.MODPACK_TYPE;
else if (manifest instanceof CurseManifest)
return CurseInstallTask.MODPACK_TYPE;
else
throw new UnsupportedModpackException();
}
public static Task<Void> getInstallTask(Profile profile, File zipFile, String name, Modpack modpack) {
profile.getRepository().markVersionAsModpack(name);
ExceptionalRunnable<?> success = () -> {
HMCLGameRepository repository = profile.getRepository();
repository.refreshVersions();
VersionSetting vs = repository.specializeVersionSetting(name);
repository.undoMark(name);
if (vs != null)
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
};
ExceptionalConsumer<Exception, ?> failure = ex -> {
if (ex instanceof CurseCompletionException && !(ex.getCause() instanceof FileNotFoundException)) {
success.run();
// This is tolerable and we will not delete the game
} else {
HMCLGameRepository repository = profile.getRepository();
repository.removeVersionFromDisk(name);
}
};
if (modpack.getManifest() instanceof CurseManifest)
return new CurseInstallTask(profile.getDependency(), zipFile, modpack, ((CurseManifest) modpack.getManifest()), name)
.whenComplete(Schedulers.defaultScheduler(), success, failure);
else if (modpack.getManifest() instanceof HMCLModpackManifest)
return new HMCLModpackInstallTask(profile, zipFile, modpack, name)
.whenComplete(Schedulers.defaultScheduler(), success, failure);
else if (modpack.getManifest() instanceof MultiMCInstanceConfiguration)
return new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, ((MultiMCInstanceConfiguration) modpack.getManifest()), name)
.whenComplete(Schedulers.defaultScheduler(), success, failure)
.thenCompose(new MultiMCInstallVersionSettingTask(profile, ((MultiMCInstanceConfiguration) modpack.getManifest()), name));
else throw new IllegalStateException("Unrecognized modpack: " + modpack);
}
public static Task<Void> getUpdateTask(Profile profile, File zipFile, Charset charset, String name, ModpackConfiguration<?> configuration) throws UnsupportedModpackException, MismatchedModpackTypeException {
Modpack modpack = ModpackHelper.readModpackManifest(zipFile.toPath(), charset);
switch (configuration.getType()) {
case CurseInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof CurseManifest))
throw new MismatchedModpackTypeException(CurseInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new ModpackUpdateTask(profile.getRepository(), name, new CurseInstallTask(profile.getDependency(), zipFile, modpack, (CurseManifest) modpack.getManifest(), name));
case MultiMCModpackInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof MultiMCInstanceConfiguration))
throw new MismatchedModpackTypeException(MultiMCModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new ModpackUpdateTask(profile.getRepository(), name, new MultiMCModpackInstallTask(profile.getDependency(), zipFile, modpack, (MultiMCInstanceConfiguration) modpack.getManifest(), name));
case HMCLModpackInstallTask.MODPACK_TYPE:
if (!(modpack.getManifest() instanceof HMCLModpackManifest))
throw new MismatchedModpackTypeException(HMCLModpackInstallTask.MODPACK_TYPE, getManifestType(modpack.getManifest()));
return new ModpackUpdateTask(profile.getRepository(), name, new HMCLModpackInstallTask(profile, zipFile, modpack, name));
default:
throw new UnsupportedModpackException();
}
}
public static void toVersionSetting(MultiMCInstanceConfiguration c, VersionSetting vs) {
vs.setUsesGlobal(false);
vs.setGameDirType(EnumGameDirectory.VERSION_FOLDER);
if (c.isOverrideJavaLocation()) {
vs.setJavaDir(Lang.nonNull(c.getJavaPath(), ""));
}
if (c.isOverrideMemory()) {
vs.setPermSize(Optional.ofNullable(c.getPermGen()).map(Object::toString).orElse(""));
if (c.getMaxMemory() != null)
vs.setMaxMemory(c.getMaxMemory());
vs.setMinMemory(c.getMinMemory());
}
if (c.isOverrideCommands()) {
vs.setWrapper(Lang.nonNull(c.getWrapperCommand(), ""));
vs.setPreLaunchCommand(Lang.nonNull(c.getPreLaunchCommand(), ""));
}
if (c.isOverrideJavaArgs()) {
vs.setJavaArgs(Lang.nonNull(c.getJvmArgs(), ""));
}
if (c.isOverrideConsole()) {
vs.setShowLogs(c.isShowConsole());
}
if (c.isOverrideWindow()) {
vs.setFullscreen(c.isFullscreen());
if (c.getWidth() != null)
vs.setWidth(c.getWidth());
if (c.getHeight() != null)
vs.setHeight(c.getHeight());
}
}
}
@@ -0,0 +1,46 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import org.jackhuang.hmcl.mod.MultiMCInstanceConfiguration;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.VersionSetting;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import java.util.Objects;
public final class MultiMCInstallVersionSettingTask extends Task<Void> {
private final Profile profile;
private final MultiMCInstanceConfiguration manifest;
private final String version;
public MultiMCInstallVersionSettingTask(Profile profile, MultiMCInstanceConfiguration manifest, String version) {
this.profile = profile;
this.manifest = manifest;
this.version = version;
setExecutor(Schedulers.javafx());
}
@Override
public void execute() {
VersionSetting vs = Objects.requireNonNull(profile.getRepository().specializeVersionSetting(version));
ModpackHelper.toVersionSetting(manifest, vs);
}
}
@@ -0,0 +1,221 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.game;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singletonMap;
import static java.util.Objects.requireNonNull;
import static org.jackhuang.hmcl.util.Lang.threadPool;
import static org.jackhuang.hmcl.util.Logging.LOG;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.EnumMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.imageio.ImageIO;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.ServerResponseMalformedException;
import org.jackhuang.hmcl.auth.yggdrasil.Texture;
import org.jackhuang.hmcl.auth.yggdrasil.TextureModel;
import org.jackhuang.hmcl.auth.yggdrasil.TextureType;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.task.FileDownloadTask;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.image.Image;
/**
* @author yushijinhun
*/
public final class TexturesLoader {
private TexturesLoader() {
}
// ==== Texture Loading ====
public static class LoadedTexture {
private final BufferedImage image;
private final Map<String, String> metadata;
public LoadedTexture(BufferedImage image, Map<String, String> metadata) {
this.image = requireNonNull(image);
this.metadata = requireNonNull(metadata);
}
public BufferedImage getImage() {
return image;
}
public Map<String, String> getMetadata() {
return metadata;
}
}
private static final ThreadPoolExecutor POOL = threadPool("TexturesDownload", true, 2, 10, TimeUnit.SECONDS);
private static final Path TEXTURES_DIR = Metadata.MINECRAFT_DIRECTORY.resolve("assets").resolve("skins");
private static Path getTexturePath(Texture texture) {
String url = texture.getUrl();
int slash = url.lastIndexOf('/');
int dot = url.lastIndexOf('.');
if (dot < slash) {
dot = url.length();
}
String hash = url.substring(slash + 1, dot);
String prefix = hash.length() > 2 ? hash.substring(0, 2) : "xx";
return TEXTURES_DIR.resolve(prefix).resolve(hash);
}
public static LoadedTexture loadTexture(Texture texture) throws IOException {
if (StringUtils.isBlank(texture.getUrl())) {
throw new IOException("Texture url is empty");
}
Path file = getTexturePath(texture);
if (!Files.isRegularFile(file)) {
// download it
try {
new FileDownloadTask(new URL(texture.getUrl()), file.toFile()).run();
LOG.info("Texture downloaded: " + texture.getUrl());
} catch (Exception e) {
if (Files.isRegularFile(file)) {
// concurrency conflict?
LOG.log(Level.WARNING, "Failed to download texture " + texture.getUrl() + ", but the file is available", e);
} else {
throw new IOException("Failed to download texture " + texture.getUrl());
}
}
}
BufferedImage img;
try (InputStream in = Files.newInputStream(file)) {
img = ImageIO.read(in);
}
Map<String, String> metadata = texture.getMetadata();
if (metadata == null) {
metadata = emptyMap();
}
return new LoadedTexture(img, metadata);
}
// ====
// ==== Skins ====
private final static Map<TextureModel, LoadedTexture> DEFAULT_SKINS = new EnumMap<>(TextureModel.class);
static {
loadDefaultSkin("/assets/img/steve.png", TextureModel.STEVE);
loadDefaultSkin("/assets/img/alex.png", TextureModel.ALEX);
}
private static void loadDefaultSkin(String path, TextureModel model) {
try (InputStream in = TexturesLoader.class.getResourceAsStream(path)) {
DEFAULT_SKINS.put(model, new LoadedTexture(ImageIO.read(in), singletonMap("model", model.modelName)));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static LoadedTexture getDefaultSkin(TextureModel model) {
return DEFAULT_SKINS.get(model);
}
public static ObjectBinding<LoadedTexture> skinBinding(YggdrasilService service, UUID uuid) {
LoadedTexture uuidFallback = getDefaultSkin(TextureModel.detectUUID(uuid));
return BindingMapping.of(service.getProfileRepository().binding(uuid))
.map(profile -> profile
.flatMap(it -> {
try {
return YggdrasilService.getTextures(it);
} catch (ServerResponseMalformedException e) {
LOG.log(Level.WARNING, "Failed to parse texture payload", e);
return Optional.empty();
}
})
.flatMap(it -> Optional.ofNullable(it.get(TextureType.SKIN)))
.filter(it -> StringUtils.isNotBlank(it.getUrl())))
.asyncMap(it -> {
if (it.isPresent()) {
Texture texture = it.get();
return CompletableFuture.supplyAsync(() -> {
try {
return loadTexture(texture);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to load texture " + texture.getUrl() + ", using fallback texture", e);
return uuidFallback;
}
}, POOL);
} else {
return CompletableFuture.completedFuture(uuidFallback);
}
}, uuidFallback);
}
// ====
// ==== Avatar ====
public static BufferedImage toAvatar(BufferedImage skin, int size) {
BufferedImage avatar = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = avatar.createGraphics();
int scale = skin.getWidth() / 64;
int faceOffset = (int) Math.round(size / 18.0);
g.drawImage(skin,
faceOffset, faceOffset, size - faceOffset, size - faceOffset,
8 * scale, 8 * scale, 16 * scale, 16 * scale,
null);
g.drawImage(skin,
0, 0, size, size,
40 * scale, 8 * scale, 48 * scale, 16 * scale, null);
g.dispose();
return avatar;
}
public static ObjectBinding<Image> fxAvatarBinding(YggdrasilService service, UUID uuid, int size) {
return BindingMapping.of(skinBinding(service, uuid))
.map(it -> toAvatar(it.image, size))
.map(img -> SwingFXUtils.toFXImage(img, null));
}
public static ObjectBinding<Image> fxAvatarBinding(Account account, int size) {
if (account instanceof YggdrasilAccount) {
return fxAvatarBinding(((YggdrasilAccount) account).getYggdrasilService(), account.getUUID(), size);
} else {
return Bindings.createObjectBinding(
() -> SwingFXUtils.toFXImage(toAvatar(getDefaultSkin(TextureModel.detectUUID(account.getUUID())).image, size), null));
}
}
// ====
}
@@ -0,0 +1,289 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import javafx.beans.Observable;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyListProperty;
import javafx.beans.property.ReadOnlyListWrapper;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ObservableList;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AccountFactory;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccountFactory;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorArtifactProvider;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloader;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.authlibinjector.SimpleAuthlibInjectorArtifactProvider;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.offline.OfflineAccountFactory;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccountFactory;
import org.jackhuang.hmcl.task.Schedulers;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.logging.Level;
import static java.util.stream.Collectors.toList;
import static javafx.collections.FXCollections.observableArrayList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.Pair.pair;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
* @author huangyuhui
*/
public final class Accounts {
private Accounts() {}
public static final OfflineAccountFactory FACTORY_OFFLINE = OfflineAccountFactory.INSTANCE;
public static final YggdrasilAccountFactory FACTORY_MOJANG = YggdrasilAccountFactory.MOJANG;
public static final AuthlibInjectorAccountFactory FACTORY_AUTHLIB_INJECTOR = new AuthlibInjectorAccountFactory(createAuthlibInjectorArtifactProvider(), Accounts::getOrCreateAuthlibInjectorServer);
// ==== login type / account factory mapping ====
private static final Map<String, AccountFactory<?>> type2factory = new HashMap<>();
private static final Map<AccountFactory<?>, String> factory2type = new HashMap<>();
static {
type2factory.put("offline", FACTORY_OFFLINE);
type2factory.put("yggdrasil", FACTORY_MOJANG);
type2factory.put("authlibInjector", FACTORY_AUTHLIB_INJECTOR);
type2factory.forEach((type, factory) -> factory2type.put(factory, type));
}
public static String getLoginType(AccountFactory<?> factory) {
return Optional.ofNullable(factory2type.get(factory))
.orElseThrow(() -> new IllegalArgumentException("Unrecognized account factory"));
}
public static AccountFactory<?> getAccountFactory(String loginType) {
return Optional.ofNullable(type2factory.get(loginType))
.orElseThrow(() -> new IllegalArgumentException("Unrecognized login type"));
}
// ====
public static AccountFactory<?> getAccountFactory(Account account) {
if (account instanceof OfflineAccount)
return FACTORY_OFFLINE;
else if (account instanceof AuthlibInjectorAccount)
return FACTORY_AUTHLIB_INJECTOR;
else if (account instanceof YggdrasilAccount)
return FACTORY_MOJANG;
else
throw new IllegalArgumentException("Failed to determine account type: " + account);
}
private static ObservableList<Account> accounts = observableArrayList(account -> new Observable[] { account });
private static ReadOnlyListWrapper<Account> accountsWrapper = new ReadOnlyListWrapper<>(Accounts.class, "accounts", accounts);
private static ObjectProperty<Account> selectedAccount = new SimpleObjectProperty<Account>(Accounts.class, "selectedAccount") {
{
accounts.addListener(onInvalidating(this::invalidated));
}
@Override
protected void invalidated() {
// this methods first checks whether the current selection is valid
// if it's valid, the underlying storage will be updated
// otherwise, the first account will be selected as an alternative(or null if accounts is empty)
Account selected = get();
if (accounts.isEmpty()) {
if (selected == null) {
// valid
} else {
// the previously selected account is gone, we can only set it to null here
set(null);
return;
}
} else {
if (accounts.contains(selected)) {
// valid
} else {
// the previously selected account is gone
set(accounts.get(0));
return;
}
}
// selection is valid, store it
if (!initialized)
return;
updateAccountStorages();
}
};
/**
* True if {@link #init()} hasn't been called.
*/
private static boolean initialized = false;
static {
accounts.addListener(onInvalidating(Accounts::updateAccountStorages));
}
private static Map<Object, Object> getAccountStorage(Account account) {
Map<Object, Object> storage = account.toStorage();
storage.put("type", getLoginType(getAccountFactory(account)));
if (account == selectedAccount.get()) {
storage.put("selected", true);
}
return storage;
}
private static void updateAccountStorages() {
// don't update the underlying storage before data loading is completed
// otherwise it might cause data loss
if (!initialized)
return;
// update storage
config().getAccountStorages().setAll(accounts.stream().map(Accounts::getAccountStorage).collect(toList()));
}
/**
* Called when it's ready to load accounts from {@link ConfigHolder#config()}.
*/
static void init() {
if (initialized)
throw new IllegalStateException("Already initialized");
// load accounts
config().getAccountStorages().forEach(storage -> {
AccountFactory<?> factory = type2factory.get(storage.get("type"));
if (factory == null) {
LOG.warning("Unrecognized account type: " + storage);
return;
}
Account account;
try {
account = factory.fromStorage(storage);
} catch (Exception e) {
LOG.log(Level.WARNING, "Failed to load account: " + storage, e);
return;
}
accounts.add(account);
if (Boolean.TRUE.equals(storage.get("selected"))) {
selectedAccount.set(account);
}
});
initialized = true;
config().getAuthlibInjectorServers().addListener(onInvalidating(Accounts::removeDanglingAuthlibInjectorAccounts));
Account selected = selectedAccount.get();
if (selected != null) {
Schedulers.io().execute(() -> {
try {
selected.logIn();
} catch (AuthenticationException e) {
LOG.log(Level.WARNING, "Failed to log " + selected + " in", e);
}
});
}
for (AuthlibInjectorServer server : config().getAuthlibInjectorServers()) {
if (selected instanceof AuthlibInjectorAccount && ((AuthlibInjectorAccount) selected).getServer() == server)
continue;
Schedulers.io().execute(() -> {
try {
server.fetchMetadataResponse();
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to fetch authlib-injector server metdata: " + server, e);
}
});
}
}
public static ObservableList<Account> getAccounts() {
return accounts;
}
public static ReadOnlyListProperty<Account> accountsProperty() {
return accountsWrapper.getReadOnlyProperty();
}
public static Account getSelectedAccount() {
return selectedAccount.get();
}
public static void setSelectedAccount(Account selectedAccount) {
Accounts.selectedAccount.set(selectedAccount);
}
public static ObjectProperty<Account> selectedAccountProperty() {
return selectedAccount;
}
// ==== authlib-injector ====
private static AuthlibInjectorArtifactProvider createAuthlibInjectorArtifactProvider() {
String authlibinjectorLocation = System.getProperty("hmcl.authlibinjector.location");
if (authlibinjectorLocation == null) {
return new AuthlibInjectorDownloader(Metadata.HMCL_DIRECTORY, DownloadProviders::getDownloadProvider);
} else {
LOG.info("Using specified authlib-injector: " + authlibinjectorLocation);
return new SimpleAuthlibInjectorArtifactProvider(Paths.get(authlibinjectorLocation));
}
}
private static AuthlibInjectorServer getOrCreateAuthlibInjectorServer(String url) {
return config().getAuthlibInjectorServers().stream()
.filter(server -> url.equals(server.getUrl()))
.findFirst()
.orElseGet(() -> {
AuthlibInjectorServer server = new AuthlibInjectorServer(url);
config().getAuthlibInjectorServers().add(server);
return server;
});
}
/**
* After an {@link AuthlibInjectorServer} is removed, the associated accounts should also be removed.
* This method performs a check and removes the dangling accounts.
*/
private static void removeDanglingAuthlibInjectorAccounts() {
accounts.stream()
.filter(AuthlibInjectorAccount.class::isInstance)
.map(AuthlibInjectorAccount.class::cast)
.filter(it -> !config().getAuthlibInjectorServers().contains(it.getServer()))
.collect(toList())
.forEach(accounts::remove);
}
// ====
// ==== Login type name i18n ===
private static Map<AccountFactory<?>, String> unlocalizedLoginTypeNames = mapOf(
pair(Accounts.FACTORY_OFFLINE, "account.methods.offline"),
pair(Accounts.FACTORY_MOJANG, "account.methods.yggdrasil"),
pair(Accounts.FACTORY_AUTHLIB_INJECTOR, "account.methods.authlib_injector"));
public static String getLocalizedLoginTypeName(AccountFactory<?> factory) {
return i18n(Optional.ofNullable(unlocalizedLoginTypeNames.get(factory))
.orElseThrow(() -> new IllegalArgumentException("Unrecognized account factory")));
}
// ====
}
+416 -311
View File
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,346 +13,451 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.core.download.DownloadType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import java.awt.Font;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.collections.ObservableSet;
import org.hildan.fxgson.creators.ObservableListCreator;
import org.hildan.fxgson.creators.ObservableMapCreator;
import org.hildan.fxgson.creators.ObservableSetCreator;
import org.hildan.fxgson.factories.JavaFxPropertyTypeAdapterFactory;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.upgrade.UpdateChannel;
import org.jackhuang.hmcl.util.gson.EnumOrdinalDeserializer;
import org.jackhuang.hmcl.util.gson.FileTypeAdapter;
import org.jackhuang.hmcl.util.i18n.Locales;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import org.jackhuang.hmcl.core.auth.AbstractAuthenticator;
import org.jackhuang.hmcl.laf.LAFTheme;
import org.jackhuang.hmcl.api.HMCLApi;
import org.jackhuang.hmcl.api.event.config.AuthenticatorChangedEvent;
import org.jackhuang.hmcl.api.event.config.DownloadTypeChangedEvent;
import org.jackhuang.hmcl.api.event.config.ThemeChangedEvent;
import org.jackhuang.hmcl.core.MCUtils;
import org.jackhuang.hmcl.util.sys.JdkVersion;
import org.jackhuang.hmcl.util.sys.OS;
import org.jackhuang.hmcl.api.auth.IAuthenticator;
import org.jackhuang.hmcl.api.ui.Theme;
/**
*
* @author huangyuhui
*/
public final class Config implements Cloneable {
public final class Config implements Cloneable, Observable {
public static final int CURRENT_UI_VERSION = 0;
private static final Gson CONFIG_GSON = new GsonBuilder()
.registerTypeAdapter(File.class, FileTypeAdapter.INSTANCE)
.registerTypeAdapter(ObservableList.class, new ObservableListCreator())
.registerTypeAdapter(ObservableSet.class, new ObservableSetCreator())
.registerTypeAdapter(ObservableMap.class, new ObservableMapCreator())
.registerTypeAdapterFactory(new JavaFxPropertyTypeAdapterFactory(true, true))
.registerTypeAdapter(EnumBackgroundImage.class, new EnumOrdinalDeserializer<>(EnumBackgroundImage.class)) // backward compatibility for backgroundType
.registerTypeAdapter(Proxy.Type.class, new EnumOrdinalDeserializer<>(Proxy.Type.class)) // backward compatibility for hasProxy
.setPrettyPrinting()
.create();
@Nullable
public static Config fromJson(String json) throws JsonParseException {
Config loaded = CONFIG_GSON.fromJson(json, Config.class);
if (loaded == null) {
return null;
}
Config instance = new Config();
PropertyUtils.copyProperties(loaded, instance);
return instance;
}
@SerializedName("last")
private String last;
private StringProperty selectedProfile = new SimpleStringProperty("");
@SerializedName("backgroundType")
private ObjectProperty<EnumBackgroundImage> backgroundImageType = new SimpleObjectProperty<>(EnumBackgroundImage.DEFAULT);
@SerializedName("bgpath")
private String bgpath;
private StringProperty backgroundImage = new SimpleStringProperty();
@SerializedName("commonDirType")
private ObjectProperty<EnumCommonDirectory> commonDirType = new SimpleObjectProperty<>(EnumCommonDirectory.DEFAULT);
@SerializedName("commonpath")
private String commonpath;
@SerializedName("clientToken")
private final String clientToken;
@SerializedName("proxyHost")
private String proxyHost;
@SerializedName("proxyPort")
private String proxyPort;
@SerializedName("proxyUserName")
private String proxyUserName;
@SerializedName("proxyPassword")
private String proxyPassword;
private StringProperty commonDirectory = new SimpleStringProperty(Metadata.MINECRAFT_DIRECTORY.toString());
@SerializedName("hasProxy")
private BooleanProperty hasProxy = new SimpleBooleanProperty();
@SerializedName("hasProxyAuth")
private BooleanProperty hasProxyAuth = new SimpleBooleanProperty();
@SerializedName("proxyType")
private int proxyType;
@SerializedName("enableShadow")
private boolean enableShadow;
@SerializedName("enableBlur")
private boolean enableBlur;
@SerializedName("enableAnimation")
private boolean enableAnimation;
@SerializedName("decorated")
private boolean decorated;
private ObjectProperty<Proxy.Type> proxyType = new SimpleObjectProperty<>(Proxy.Type.HTTP);
@SerializedName("proxyHost")
private StringProperty proxyHost = new SimpleStringProperty();
@SerializedName("proxyPort")
private IntegerProperty proxyPort = new SimpleIntegerProperty();
@SerializedName("proxyUserName")
private StringProperty proxyUser = new SimpleStringProperty();
@SerializedName("proxyPassword")
private StringProperty proxyPass = new SimpleStringProperty();
@SerializedName("theme")
private String theme;
@SerializedName("java")
private List<JdkVersion> java;
private ObjectProperty<Theme> theme = new SimpleObjectProperty<>(Theme.BLUE);
@SerializedName("localization")
private String localization;
@SerializedName("logintype")
private int logintype;
@SerializedName("downloadtype")
private int downloadtype;
private ObjectProperty<SupportedLocale> localization = new SimpleObjectProperty<>(Locales.DEFAULT);
@SerializedName("downloadType")
private StringProperty downloadType = new SimpleStringProperty("bmclapi");
@SerializedName("configurations")
private TreeMap<String, Profile> configurations;
@SerializedName("auth")
private Map<String, Map> auth;
private ObservableMap<String, Profile> configurations = FXCollections.observableMap(new TreeMap<>());
@SerializedName("accounts")
private ObservableList<Map<Object, Object>> accountStorages = FXCollections.observableArrayList();
@SerializedName("fontFamily")
private String fontFamily;
private StringProperty fontFamily = new SimpleStringProperty("Consolas");
@SerializedName("fontSize")
private int fontSize;
private DoubleProperty fontSize = new SimpleDoubleProperty(12);
@SerializedName("logLines")
private int logLines;
private IntegerProperty logLines = new SimpleIntegerProperty(100);
public Config() {
clientToken = UUID.randomUUID().toString();
proxyType = logintype = downloadtype = 0;
logLines = 100;
enableAnimation = enableBlur = true;
if (OS.os() == OS.WINDOWS)
enableShadow = true;
theme = LAFTheme.BLUE.id;
decorated = OS.os() == OS.LINUX;
auth = new HashMap<>();
Font font = Font.decode("Consolas");
if (font == null)
font = Font.decode("Monospace");
if (font != null)
fontFamily = font.getFamily();
fontSize = 12;
commonpath = MCUtils.getLocation().getPath();
}
@SerializedName("authlibInjectorServers")
private ObservableList<AuthlibInjectorServer> authlibInjectorServers = FXCollections.observableArrayList(server -> new Observable[] { server });
public List<JdkVersion> getJava() {
return java == null ? java = new ArrayList<>() : java;
}
@SerializedName("updateChannel")
private ObjectProperty<UpdateChannel> updateChannel = new SimpleObjectProperty<>(UpdateChannel.STABLE);
public Theme getTheme() {
if (!Theme.THEMES.containsKey(theme))
theme = LAFTheme.BLUE.id;
return Theme.THEMES.get(theme);
}
public void setTheme(String theme) {
this.theme = theme;
HMCLApi.EVENT_BUS.fireChannel(new ThemeChangedEvent(this, getTheme()));
Settings.save();
}
public boolean isDecorated() {
return decorated;
}
public void setDecorated(boolean decorated) {
this.decorated = decorated;
}
public boolean isEnableShadow() {
return enableShadow && OS.os() == OS.WINDOWS;
}
public void setEnableShadow(boolean enableShadow) {
this.enableShadow = enableShadow;
Settings.save();
}
public boolean isEnableAnimation() {
return enableAnimation;
}
public void setEnableAnimation(boolean enableAnimation) {
this.enableAnimation = enableAnimation;
Settings.save();
}
public boolean isEnableBlur() {
return enableBlur;
}
public void setEnableBlur(boolean enableBlur) {
this.enableBlur = enableBlur;
Settings.save();
}
@SerializedName("_version")
private IntegerProperty configVersion = new SimpleIntegerProperty(0);
/**
* Last selected profile name.
* The version of UI that the user have last used.
* If there is a major change in UI, {@link Config#CURRENT_UI_VERSION} should be increased.
* When {@link #CURRENT_UI_VERSION} is higher than the property, the user guide should be shown,
* then this property is set to the same value as {@link #CURRENT_UI_VERSION}.
* In particular, the property is default to 0, so that whoever open the application for the first time will see the guide.
*/
public String getLast() {
if (last == null)
last = Settings.DEFAULT_PROFILE;
return last;
}
@SerializedName("uiVersion")
private IntegerProperty uiVersion = new SimpleIntegerProperty(0);
public void setLast(String last) {
this.last = last;
Settings.onProfileChanged();
Settings.save();
}
/**
* The preferred login type to use when the user wants to add an account.
*/
@SerializedName("preferredLoginType")
private StringProperty preferredLoginType = new SimpleStringProperty();
public String getBgpath() {
return bgpath;
}
private transient ObservableHelper helper = new ObservableHelper(this);
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 String getProxyHost() {
return proxyHost == null ? "" : proxyHost;
}
public void setProxyHost(String proxyHost) {
this.proxyHost = proxyHost;
Settings.save();
}
public String getProxyPort() {
return proxyPort == null ? "" : proxyPort;
}
public void setProxyPort(String proxyPort) {
this.proxyPort = proxyPort;
Settings.save();
}
public Proxy.Type getProxyType() {
if (proxyType < 0 || proxyType >= 3) {
proxyType = 0;
Settings.save();
}
return Proxy.Type.values()[proxyType];
}
public void setProxyType(Proxy.Type proxyType) {
this.proxyType = proxyType.ordinal();
Settings.save();
}
public String getProxyUserName() {
return proxyUserName == null ? "" : proxyUserName;
}
public void setProxyUserName(String proxyUserName) {
this.proxyUserName = proxyUserName;
Settings.save();
}
public String getProxyPassword() {
return proxyPassword == null ? "" : proxyPassword;
}
public void setProxyPassword(String proxyPassword) {
this.proxyPassword = proxyPassword;
Settings.save();
}
public int getFontSize() {
return fontSize;
}
public void setFontSize(int fontSize) {
this.fontSize = fontSize;
Settings.save();
}
public Font getConsoleFont() {
return Font.decode(fontFamily + "-" + fontSize);
}
public String getFontFamily() {
return Font.decode(fontFamily).getFamily();
}
public void setFontFamily(String fontFamily) {
this.fontFamily = Font.decode(fontFamily).getFamily(); // avoid invalid font family
Settings.save();
}
public String getLocalization() {
return localization;
}
public void setLocalization(String localization) {
this.localization = localization;
Settings.save();
}
public int getLogLines() {
if (logLines == 100 || logLines == 1000 || logLines == 5000)
return logLines;
else
return logLines = 100;
}
public void setLogLines(int logLines) {
this.logLines = logLines;
Settings.save();
public Config() {
PropertyUtils.attachListener(this, helper);
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
public void addListener(InvalidationListener listener) {
helper.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
helper.removeListener(listener);
}
public String toJson() {
return CONFIG_GSON.toJson(this);
}
@Override
public Config clone() {
return fromJson(this.toJson());
}
// Getters & Setters & Properties
public String getSelectedProfile() {
return selectedProfile.get();
}
public void setSelectedProfile(String selectedProfile) {
this.selectedProfile.set(selectedProfile);
}
public StringProperty selectedProfileProperty() {
return selectedProfile;
}
public EnumBackgroundImage getBackgroundImageType() {
return backgroundImageType.get();
}
public void setBackgroundImageType(EnumBackgroundImage backgroundImageType) {
this.backgroundImageType.set(backgroundImageType);
}
public ObjectProperty<EnumBackgroundImage> backgroundImageTypeProperty() {
return backgroundImageType;
}
public String getBackgroundImage() {
return backgroundImage.get();
}
public void setBackgroundImage(String backgroundImage) {
this.backgroundImage.set(backgroundImage);
}
public StringProperty backgroundImageProperty() {
return backgroundImage;
}
public EnumCommonDirectory getCommonDirType() {
return commonDirType.get();
}
public ObjectProperty<EnumCommonDirectory> commonDirTypeProperty() {
return commonDirType;
}
public void setCommonDirType(EnumCommonDirectory commonDirType) {
this.commonDirType.set(commonDirType);
}
public String getCommonDirectory() {
return commonDirectory.get();
}
public void setCommonDirectory(String commonDirectory) {
this.commonDirectory.set(commonDirectory);
}
public StringProperty commonDirectoryProperty() {
return commonDirectory;
}
public boolean hasProxy() {
return hasProxy.get();
}
public void setHasProxy(boolean hasProxy) {
this.hasProxy.set(hasProxy);
}
public BooleanProperty hasProxyProperty() {
return hasProxy;
}
public boolean hasProxyAuth() {
return hasProxyAuth.get();
}
public void setHasProxyAuth(boolean hasProxyAuth) {
this.hasProxyAuth.set(hasProxyAuth);
}
public BooleanProperty hasProxyAuthProperty() {
return hasProxyAuth;
}
public Proxy.Type getProxyType() {
return proxyType.get();
}
public void setProxyType(Proxy.Type proxyType) {
this.proxyType.set(proxyType);
}
public ObjectProperty<Proxy.Type> proxyTypeProperty() {
return proxyType;
}
public String getProxyHost() {
return proxyHost.get();
}
public void setProxyHost(String proxyHost) {
this.proxyHost.set(proxyHost);
}
public StringProperty proxyHostProperty() {
return proxyHost;
}
public int getProxyPort() {
return proxyPort.get();
}
public void setProxyPort(int proxyPort) {
this.proxyPort.set(proxyPort);
}
public IntegerProperty proxyPortProperty() {
return proxyPort;
}
public String getProxyUser() {
return proxyUser.get();
}
public void setProxyUser(String proxyUser) {
this.proxyUser.set(proxyUser);
}
public StringProperty proxyUserProperty() {
return proxyUser;
}
public String getProxyPass() {
return proxyPass.get();
}
public void setProxyPass(String proxyPass) {
this.proxyPass.set(proxyPass);
}
public StringProperty proxyPassProperty() {
return proxyPass;
}
public Theme getTheme() {
return theme.get();
}
public void setTheme(Theme theme) {
this.theme.set(theme);
}
public ObjectProperty<Theme> themeProperty() {
return theme;
}
public SupportedLocale getLocalization() {
return localization.get();
}
public void setLocalization(SupportedLocale localization) {
this.localization.set(localization);
}
public ObjectProperty<SupportedLocale> localizationProperty() {
return localization;
}
public String getDownloadType() {
return downloadType.get();
}
public void setDownloadType(String downloadType) {
this.downloadType.set(downloadType);
}
public StringProperty downloadTypeProperty() {
return downloadType;
}
public ObservableMap<String, Profile> getConfigurations() {
return configurations;
}
public ObservableList<Map<Object, Object>> getAccountStorages() {
return accountStorages;
}
public String getFontFamily() {
return fontFamily.get();
}
public void setFontFamily(String fontFamily) {
this.fontFamily.set(fontFamily);
}
public StringProperty fontFamilyProperty() {
return fontFamily;
}
public double getFontSize() {
return fontSize.get();
}
public void setFontSize(double fontSize) {
this.fontSize.set(fontSize);
}
public DoubleProperty fontSizeProperty() {
return fontSize;
}
public int getLogLines() {
return logLines.get();
}
public void setLogLines(int logLines) {
this.logLines.set(logLines);
}
public IntegerProperty logLinesProperty() {
return logLines;
}
public ObservableList<AuthlibInjectorServer> getAuthlibInjectorServers() {
return authlibInjectorServers;
}
public UpdateChannel getUpdateChannel() {
return updateChannel.get();
}
public ObjectProperty<UpdateChannel> updateChannelProperty() {
return updateChannel;
}
public void setUpdateChannel(UpdateChannel updateChannel) {
this.updateChannel.set(updateChannel);
}
public int getConfigVersion() {
return configVersion.get();
}
public IntegerProperty configVersionProperty() {
return configVersion;
}
public void setConfigVersion(int configVersion) {
this.configVersion.set(configVersion);
}
public int getUiVersion() {
return uiVersion.get();
}
public IntegerProperty uiVersionProperty() {
return uiVersion;
}
public void setUiVersion(int uiVersion) {
this.uiVersion.set(uiVersion);
}
public String getPreferredLoginType() {
return preferredLoginType.get();
}
public void setPreferredLoginType(String preferredLoginType) {
this.preferredLoginType.set(preferredLoginType);
}
public StringProperty preferredLoginTypeProperty() {
return preferredLoginType;
}
}
@@ -0,0 +1,143 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import org.jackhuang.hmcl.util.InvocationDispatcher;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.logging.Level;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.jackhuang.hmcl.util.Logging.LOG;
public final class ConfigHolder {
private ConfigHolder() {}
public static final String CONFIG_FILENAME = "hmcl.json";
public static final String CONFIG_FILENAME_LINUX = ".hmcl.json";
private static Path configLocation;
private static Config configInstance;
private static boolean newlyCreated;
public static Config config() {
if (configInstance == null) {
throw new IllegalStateException("Configuration hasn't been loaded");
}
return configInstance;
}
public synchronized static void init() throws IOException {
if (configInstance != null) {
throw new IllegalStateException("Configuration is already loaded");
}
configLocation = locateConfig();
configInstance = loadConfig();
configInstance.addListener(source -> markConfigDirty());
Settings.init();
if (newlyCreated) {
saveConfigSync();
// hide the config file on windows
if (OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS) {
try {
Files.setAttribute(configLocation, "dos:hidden", true);
} catch (IOException e) {
LOG.log(Level.WARNING, "Failed to set hidden attribute of " + configLocation, e);
}
}
}
if (!Files.isWritable(configLocation)) {
// the config cannot be saved
// throw up the error now to prevent further data loss
throw new IOException("Config at " + configLocation + " is not writable");
}
}
private static Path locateConfig() {
Path config = Paths.get(CONFIG_FILENAME);
if (Files.isRegularFile(config))
return config;
Path dotConfig = Paths.get(CONFIG_FILENAME_LINUX);
if (Files.isRegularFile(dotConfig))
return dotConfig;
// create new
return OperatingSystem.CURRENT_OS == OperatingSystem.WINDOWS ? config : dotConfig;
}
private static Config loadConfig() throws IOException {
if (Files.exists(configLocation)) {
try {
String content = FileUtils.readText(configLocation);
Config deserialized = Config.fromJson(content);
if (deserialized == null) {
LOG.info("Config is empty");
} else {
Map<?, ?> raw = new Gson().fromJson(content, Map.class);
ConfigUpgrader.upgradeConfig(deserialized, raw);
return deserialized;
}
} catch (JsonParseException e) {
LOG.log(Level.WARNING, "Malformed config.", e);
}
}
LOG.info("Creating an empty config");
newlyCreated = true;
return new Config();
}
private static InvocationDispatcher<String> configWriter = InvocationDispatcher.runOn(Lang::thread, content -> {
try {
writeToConfig(content);
} catch (IOException e) {
LOG.log(Level.SEVERE, "Failed to save config", e);
}
});
private static void writeToConfig(String content) throws IOException {
LOG.info("Saving config");
synchronized (configLocation) {
Files.write(configLocation, content.getBytes(UTF_8));
}
}
static void markConfigDirty() {
configWriter.accept(configInstance.toJson());
}
private static void saveConfigSync() throws IOException {
writeToConfig(configInstance.toJson());
}
}
@@ -0,0 +1,129 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import org.jackhuang.hmcl.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import static org.jackhuang.hmcl.util.Lang.tryCast;
final class ConfigUpgrader {
private static final int VERSION = 0;
private ConfigUpgrader() {
}
/**
* This method is for the compatibility with old HMCL 3.x as well as HMCL 2.x.
*
* @param deserialized deserialized config settings
* @param rawJson raw json structure of the config settings without modification
* @return true if config version is upgraded
*/
static boolean upgradeConfig(Config deserialized, Map<?, ?> rawJson) {
boolean upgraded;
if (deserialized.getConfigVersion() < VERSION) {
deserialized.setConfigVersion(VERSION);
// TODO: Add upgrade code here.
upgraded = true;
} else {
upgraded = false;
}
upgradeV2(deserialized, rawJson);
upgradeV3(deserialized, rawJson);
return upgraded;
}
/**
* Upgrade configuration of HMCL 2.x
*
* @param deserialized deserialized config settings
* @param rawJson raw json structure of the config settings without modification
*/
private static void upgradeV2(Config deserialized, Map<?, ?> rawJson) {
// Convert OfflineAccounts whose stored uuid is important.
tryCast(rawJson.get("auth"), Map.class).ifPresent(auth -> {
tryCast(auth.get("offline"), Map.class).ifPresent(offline -> {
String selected = rawJson.containsKey("selectedAccount") ? null
: tryCast(offline.get("IAuthenticator_UserName"), String.class).orElse(null);
tryCast(offline.get("uuidMap"), Map.class).ifPresent(uuidMap -> {
((Map<?, ?>) uuidMap).forEach((key, value) -> {
Map<Object, Object> storage = new HashMap<>();
storage.put("type", "offline");
storage.put("username", key);
storage.put("uuid", value);
if (key.equals(selected)) {
storage.put("selected", true);
}
deserialized.getAccountStorages().add(storage);
});
});
});
});
}
/**
* Upgrade configuration of HMCL earlier than 3.1.70
*
* @param deserialized deserialized config settings
* @param rawJson raw json structure of the config settings without modification
*/
private static void upgradeV3(Config deserialized, Map<?, ?> rawJson) {
if (!rawJson.containsKey("commonDirType"))
deserialized.setCommonDirType(deserialized.getCommonDirectory().equals(Settings.getDefaultCommonDirectory()) ? EnumCommonDirectory.DEFAULT : EnumCommonDirectory.CUSTOM);
if (!rawJson.containsKey("backgroundType"))
deserialized.setBackgroundImageType(StringUtils.isNotBlank(deserialized.getBackgroundImage()) ? EnumBackgroundImage.CUSTOM : EnumBackgroundImage.DEFAULT);
if (!rawJson.containsKey("hasProxy"))
deserialized.setHasProxy(StringUtils.isNotBlank(deserialized.getProxyHost()));
if (!rawJson.containsKey("hasProxyAuth"))
deserialized.setHasProxyAuth(StringUtils.isNotBlank(deserialized.getProxyUser()));
if (!rawJson.containsKey("downloadType")) {
tryCast(rawJson.get("downloadtype"), Number.class)
.map(Number::intValue)
.ifPresent(id -> {
if (id == 0) {
deserialized.setDownloadType("mojang");
} else if (id == 1) {
deserialized.setDownloadType("bmclapi");
}
});
}
tryCast(rawJson.get("selectedAccount"), String.class)
.ifPresent(selected -> {
deserialized.getAccountStorages().stream()
.filter(storage -> {
Object type = storage.get("type");
if ("offline".equals(type)) {
return selected.equals(storage.get("username") + ":" + storage.get("username"));
} else if ("yggdrasil".equals(type) || "authlibInjector".equals(type)) {
return selected.equals(storage.get("username") + ":" + storage.get("displayName"));
} else {
return false;
}
})
.findFirst()
.ifPresent(storage -> storage.put("selected", true));
});
}
}
@@ -0,0 +1,60 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.mapOf;
import static org.jackhuang.hmcl.util.Pair.pair;
import java.util.Map;
import java.util.Optional;
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider;
import org.jackhuang.hmcl.download.DownloadProvider;
import org.jackhuang.hmcl.download.MojangDownloadProvider;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ObservableObjectValue;
public final class DownloadProviders {
private DownloadProviders() {}
public static final Map<String, DownloadProvider> providersById = mapOf(
pair("mojang", new MojangDownloadProvider()),
pair("bmclapi", new BMCLAPIDownloadProvider()));
public static final String DEFAULT_PROVIDER_ID = "bmclapi";
private static ObjectBinding<DownloadProvider> downloadProviderProperty;
static void init() {
downloadProviderProperty = Bindings.createObjectBinding(
() -> Optional.ofNullable(providersById.get(config().getDownloadType()))
.orElse(providersById.get(DEFAULT_PROVIDER_ID)),
config().downloadTypeProperty());
}
public static DownloadProvider getDownloadProvider() {
return downloadProviderProperty.get();
}
public static ObservableObjectValue<DownloadProvider> downloadProviderProperty() {
return downloadProviderProperty;
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft!.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,15 +13,11 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.api.func;
package org.jackhuang.hmcl.setting;
/**
*
* @author huangyuhui
*/
public interface Function<T, R> {
R apply(T t);
public enum EnumBackgroundImage {
DEFAULT,
CUSTOM
}
@@ -0,0 +1,29 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
public enum EnumCommonDirectory {
/**
* %appdata%/.minecraft or ~/.minecraft
*/
DEFAULT,
/**
* user customized common directory.
*/
CUSTOM
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft!.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,24 +13,26 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.task.comm;
import org.jackhuang.hmcl.util.task.Task;
package org.jackhuang.hmcl.setting;
/**
* Determines where game runs in and game files such as mods.
*
* @author huangyuhui
* @param <T> Previous task result type
*/
public interface PreviousResultRegistrar<T> {
public enum EnumGameDirectory {
/**
*
* @param pr previous task handler
*
* @return task self instance(factory mode!)
* .minecraft
*/
Task registerPreviousResult(PreviousResult<T> pr);
}
ROOT_FOLDER,
/**
* .minecraft/versions/&lt;version name&gt;
*/
VERSION_FOLDER,
/**
* user customized directory.
*/
CUSTOM
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,12 +13,12 @@
* 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/}.
* along with this program. If not, see <https://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.
*/
+192 -133
View File
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,170 +13,229 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 com.google.gson.annotations.JsonAdapter;
import com.jfoenix.utils.JFXUtilities;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.*;
import org.jackhuang.hmcl.download.DefaultDependencyManager;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.EventPriority;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.game.HMCLCacheRepository;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.ui.WeakListenerHolder;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.javafx.ObservableHelper;
import 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;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
/**
*
* @author huangyuhui
*/
public final class Profile implements IProfile {
@JsonAdapter(Profile.Serializer.class)
public final class Profile implements Observable {
private final WeakListenerHolder listenerHolder = new WeakListenerHolder();
private final HMCLGameRepository repository;
@SerializedName("selectedMinecraftVersion")
private String selectedVersion = "";
@SerializedName("gameDir")
private String gameDir;
@SerializedName("noCommon")
private boolean noCommon;
@SerializedName("global")
private VersionSetting global;
private final StringProperty selectedVersion = new SimpleStringProperty();
private transient String name;
private transient HMCLMinecraftService service = new HMCLMinecraftService(this);
private transient HMCLGameLauncher launcher = new HMCLGameLauncher(this);
public transient final EventHandler<PropertyChangedEvent<String>> propertyChanged = new EventHandler<>();
public Profile() {
this("Default");
public StringProperty selectedVersionProperty() {
return selectedVersion;
}
public Profile(String name) {
this(name, ".minecraft");
}
public Profile(String name, String gameDir) {
this.name = name;
this.gameDir = gameDir;
this.global = new VersionSetting();
}
public Profile(String name, Profile v) {
this();
if (v == null)
return;
this.name = name;
gameDir = v.gameDir;
}
@Override
public HMCLMinecraftService service() {
return service;
}
public HMCLGameLauncher launcher() {
return launcher;
}
public VersionSetting getSelectedVersionSetting() {
return getVersionSetting(getSelectedVersion());
}
public VersionSetting getVersionSetting(String id) {
VersionSetting vs = service().getVersionSetting(id);
if (vs == null || vs.isUsesGlobal()) {
global.isGlobal = true;
global.id = id;
return global;
} else
return vs;
}
public boolean isVersionSettingGlobe(String id) {
VersionSetting vs = service().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) {
VersionSetting vs = service().getVersionSetting(id);
if (vs == null)
return;
vs.setUsesGlobal(true);
propertyChanged.fire(new PropertyChangedEvent<>(this, "selectedVersion", selectedVersion, selectedVersion));
}
@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);
}
return StrUtils.isBlank(v) ? null : v;
return selectedVersion.get();
}
@Override
public void setSelectedVersion(String newVersion) {
PropertyChangedEvent event = new PropertyChangedEvent<>(this, "selectedVersion", this.selectedVersion, newVersion);
this.selectedVersion = newVersion;
propertyChanged.fire(event);
public void setSelectedVersion(String selectedVersion) {
this.selectedVersion.set(selectedVersion);
}
private final ObjectProperty<File> gameDir;
public ObjectProperty<File> gameDirProperty() {
return gameDir;
}
@Override
public File getGameDir() {
if (StrUtils.isBlank(gameDir))
gameDir = MCUtils.getInitGameDir().getPath();
return new File(gameDir.replace('\\', '/'));
return gameDir.get();
}
@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);
this.gameDir.set(gameDir);
}
public boolean isNoCommon() {
return noCommon;
private final ReadOnlyObjectWrapper<VersionSetting> global = new ReadOnlyObjectWrapper<>(this, "global");
public ReadOnlyObjectProperty<VersionSetting> globalProperty() {
return global.getReadOnlyProperty();
}
public void setNoCommon(boolean noCommon) {
PropertyChangedEvent event = new PropertyChangedEvent(this, "noCommon", this.noCommon, noCommon);
this.noCommon = noCommon;
propertyChanged.fire(event);
public VersionSetting getGlobal() {
return global.get();
}
@Override
public String getName() {
private final SimpleStringProperty name;
public StringProperty nameProperty() {
return name;
}
void setName(String name) {
PropertyChangedEvent event = new PropertyChangedEvent<>(this, "name", this.name, name);
this.name = name;
propertyChanged.fire(event);
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
private BooleanProperty useRelativePath = new SimpleBooleanProperty(this, "useRelativePath", false);
public BooleanProperty useRelativePathProperty() {
return useRelativePath;
}
public boolean isUseRelativePath() {
return useRelativePath.get();
}
public void setUseRelativePath(boolean useRelativePath) {
this.useRelativePath.set(useRelativePath);
}
public Profile(String name) {
this(name, new File(".minecraft"));
}
public Profile(String name, File initialGameDir) {
this(name, initialGameDir, new VersionSetting());
}
public Profile(String name, File initialGameDir, VersionSetting global) {
this(name, initialGameDir, global, null, false);
}
public Profile(String name, File initialGameDir, VersionSetting global, String selectedVersion, boolean useRelativePath) {
this.name = new SimpleStringProperty(this, "name", name);
gameDir = new SimpleObjectProperty<>(this, "gameDir", initialGameDir);
repository = new HMCLGameRepository(this, initialGameDir);
this.global.set(global == null ? new VersionSetting() : global);
this.selectedVersion.set(selectedVersion);
this.useRelativePath.set(useRelativePath);
gameDir.addListener((a, b, newValue) -> repository.changeDirectory(newValue));
this.selectedVersion.addListener(o -> checkSelectedVersion());
listenerHolder.add(EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> checkSelectedVersion(), EventPriority.HIGHEST));
addPropertyChangedListener(onInvalidating(this::invalidate));
}
private void checkSelectedVersion() {
JFXUtilities.runInFX(() -> {
if (!repository.isLoaded()) return;
String newValue = selectedVersion.get();
if (!repository.hasVersion(newValue)) {
Optional<String> version = repository.getVersions().stream().findFirst().map(Version::getId);
if (version.isPresent())
selectedVersion.setValue(version.get());
else if (newValue != null)
selectedVersion.setValue(null);
}
});
}
public HMCLGameRepository getRepository() {
return repository;
}
public DefaultDependencyManager getDependency() {
return new DefaultDependencyManager(repository, DownloadProviders.getDownloadProvider(), HMCLCacheRepository.REPOSITORY);
}
public VersionSetting getVersionSetting(String id) {
VersionSetting vs = repository.getVersionSetting(id);
if (vs == null || vs.isUsesGlobal()) {
getGlobal().setGlobal(true); // always keep global.isGlobal = true
getGlobal().setUsesGlobal(true);
return getGlobal();
} else
return vs;
}
@Override
public void onSelected() {
service().version().refreshVersions();
public String toString() {
return new ToStringBuilder(this)
.append("gameDir", getGameDir())
.append("name", getName())
.append("useRelativePath", isUseRelativePath())
.toString();
}
private void addPropertyChangedListener(InvalidationListener listener) {
name.addListener(listener);
global.addListener(listener);
gameDir.addListener(listener);
useRelativePath.addListener(listener);
global.get().addPropertyChangedListener(listener);
selectedVersion.addListener(listener);
}
private ObservableHelper observableHelper = new ObservableHelper(this);
@Override
public void addListener(InvalidationListener listener) {
observableHelper.addListener(listener);
}
@Override
public void removeListener(InvalidationListener listener) {
observableHelper.removeListener(listener);
}
private void invalidate() {
Platform.runLater(observableHelper::invalidate);
}
public static final class Serializer implements JsonSerializer<Profile>, JsonDeserializer<Profile> {
@Override
public JsonElement serialize(Profile src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null)
return JsonNull.INSTANCE;
JsonObject jsonObject = new JsonObject();
jsonObject.add("global", context.serialize(src.getGlobal()));
jsonObject.addProperty("gameDir", src.getGameDir().getPath());
jsonObject.addProperty("useRelativePath", src.isUseRelativePath());
jsonObject.addProperty("selectedMinecraftVersion", src.getSelectedVersion());
return jsonObject;
}
@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("");
return new Profile("Default",
new File(gameDir),
context.deserialize(obj.get("global"), VersionSetting.class),
Optional.ofNullable(obj.get("selectedMinecraftVersion")).map(JsonElement::getAsString).orElse(""),
Optional.ofNullable(obj.get("useRelativePath")).map(JsonElement::getAsBoolean).orElse(false));
}
}
}
@@ -0,0 +1,215 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import com.jfoenix.utils.JFXUtilities;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.*;
import javafx.collections.ObservableList;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import java.io.File;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static javafx.collections.FXCollections.observableArrayList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.onInvalidating;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Profiles {
public static final String DEFAULT_PROFILE = "Default";
public static final String HOME_PROFILE = "Home";
private Profiles() {
}
public static String getProfileDisplayName(Profile profile) {
switch (profile.getName()) {
case Profiles.DEFAULT_PROFILE:
return i18n("profile.default");
case Profiles.HOME_PROFILE:
return i18n("profile.home");
default:
return profile.getName();
}
}
private static final ObservableList<Profile> profiles = observableArrayList(profile -> new Observable[] { profile });
private static final ReadOnlyListWrapper<Profile> profilesWrapper = new ReadOnlyListWrapper<>(profiles);
private static ObjectProperty<Profile> selectedProfile = new SimpleObjectProperty<Profile>() {
{
profiles.addListener(onInvalidating(this::invalidated));
}
@Override
protected void invalidated() {
if (!initialized)
return;
Profile profile = get();
if (profiles.isEmpty()) {
if (profile != null) {
set(null);
return;
}
} else {
if (!profiles.contains(profile)) {
set(profiles.get(0));
return;
}
}
config().setSelectedProfile(profile == null ? "" : profile.getName());
if (profile != null) {
if (profile.getRepository().isLoaded())
selectedVersion.bind(profile.selectedVersionProperty());
else {
selectedVersion.unbind();
selectedVersion.set(null);
// bind when repository was reloaded.
profile.getRepository().refreshVersionsAsync().start();
}
} else {
selectedVersion.unbind();
selectedVersion.set(null);
}
}
};
private static void checkProfiles() {
if (profiles.isEmpty()) {
Profile current = new Profile(Profiles.DEFAULT_PROFILE, new File(".minecraft"), new VersionSetting(), null, true);
Profile home = new Profile(Profiles.HOME_PROFILE, Metadata.MINECRAFT_DIRECTORY.toFile());
Platform.runLater(() -> profiles.addAll(current, home));
}
}
/**
* True if {@link #init()} hasn't been called.
*/
private static boolean initialized = false;
static {
profiles.addListener(onInvalidating(Profiles::updateProfileStorages));
profiles.addListener(onInvalidating(Profiles::checkProfiles));
selectedProfile.addListener((a, b, newValue) -> {
if (newValue != null)
newValue.getRepository().refreshVersionsAsync().start();
});
}
private static void updateProfileStorages() {
// don't update the underlying storage before data loading is completed
// otherwise it might cause data loss
if (!initialized)
return;
// update storage
config().getConfigurations().clear();
config().getConfigurations().putAll(profiles.stream().collect(Collectors.toMap(Profile::getName, it -> it)));
}
/**
* Called when it's ready to load profiles from {@link ConfigHolder#config()}.
*/
static void init() {
if (initialized)
throw new IllegalStateException("Already initialized");
HashSet<String> names = new HashSet<>();
config().getConfigurations().forEach((name, profile) -> {
if (!names.add(name)) return;
profiles.add(profile);
profile.setName(name);
});
checkProfiles();
// Platform.runLater is necessary or profiles will be empty
// since checkProfiles adds 2 base profile later.
Platform.runLater(() -> {
initialized = true;
selectedProfile.set(
profiles.stream()
.filter(it -> it.getName().equals(config().getSelectedProfile()))
.findFirst()
.orElse(profiles.get(0)));
});
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).registerWeak(event -> {
JFXUtilities.runInFX(() -> {
Profile profile = selectedProfile.get();
if (profile != null && profile.getRepository() == event.getSource()) {
selectedVersion.bind(profile.selectedVersionProperty());
for (Consumer<Profile> listener : versionsListeners)
listener.accept(profile);
}
});
});
}
public static ObservableList<Profile> getProfiles() {
return profiles;
}
public static ReadOnlyListProperty<Profile> profilesProperty() {
return profilesWrapper.getReadOnlyProperty();
}
public static Profile getSelectedProfile() {
return selectedProfile.get();
}
public static void setSelectedProfile(Profile profile) {
selectedProfile.set(profile);
}
public static ObjectProperty<Profile> selectedProfileProperty() {
return selectedProfile;
}
private static final ReadOnlyStringWrapper selectedVersion = new ReadOnlyStringWrapper();
public static ReadOnlyStringProperty selectedVersionProperty() {
return selectedVersion.getReadOnlyProperty();
}
// Guaranteed that the repository is loaded.
public static String getSelectedVersion() {
return selectedVersion.get();
}
private static final List<Consumer<Profile>> versionsListeners = new LinkedList<>();
public static void registerVersionsListener(Consumer<Profile> listener) {
Profile profile = getSelectedProfile();
if (profile != null && profile.getRepository().isLoaded())
listener.accept(profile);
versionsListeners.add(listener);
}
}
@@ -0,0 +1,116 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ObservableObjectValue;
import org.jackhuang.hmcl.util.StringUtils;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.Proxy.Type;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Logging.LOG;
public final class ProxyManager {
private ProxyManager() {
}
private static ObjectBinding<Proxy> proxyProperty;
public static Proxy getProxy() {
return proxyProperty.get();
}
public static ObservableObjectValue<Proxy> proxyProperty() {
return proxyProperty;
}
static void init() {
proxyProperty = Bindings.createObjectBinding(
() -> {
String host = config().getProxyHost();
int port = config().getProxyPort();
if (!config().hasProxy() || StringUtils.isBlank(host) || config().getProxyType() == Proxy.Type.DIRECT) {
return Proxy.NO_PROXY;
} else {
if (port < 0 || port > 0xFFFF) {
LOG.warning("Illegal proxy port: " + port);
return Proxy.NO_PROXY;
}
return new Proxy(config().getProxyType(), new InetSocketAddress(host, port));
}
},
config().proxyTypeProperty(),
config().proxyHostProperty(),
config().proxyPortProperty(),
config().hasProxyProperty());
proxyProperty.addListener(any -> updateSystemProxy());
updateSystemProxy();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
if (config().hasProxyAuth()) {
String username = config().getProxyUser();
String password = config().getProxyPass();
if (username != null && password != null) {
return new PasswordAuthentication(username, password.toCharArray());
}
}
return null;
}
});
}
private static void updateSystemProxy() {
Proxy proxy = proxyProperty.get();
if (proxy.type() == Proxy.Type.DIRECT) {
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyHost");
System.clearProperty("https.proxyPort");
System.clearProperty("socksProxyHost");
System.clearProperty("socksProxyPort");
} else {
InetSocketAddress address = (InetSocketAddress) proxy.address();
String host = address.getHostString();
String port = String.valueOf(address.getPort());
if (proxy.type() == Type.HTTP) {
System.clearProperty("socksProxyHost");
System.clearProperty("socksProxyPort");
System.setProperty("http.proxyHost", host);
System.setProperty("http.proxyPort", port);
System.setProperty("https.proxyHost", host);
System.setProperty("https.proxyPort", port);
} else if (proxy.type() == Type.SOCKS) {
System.clearProperty("http.proxyHost");
System.clearProperty("http.proxyPort");
System.clearProperty("https.proxyHost");
System.clearProperty("https.proxyPort");
System.setProperty("socksProxyHost", host);
System.setProperty("socksProxyPort", port);
}
}
}
}
+49 -145
View File
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,160 +13,64 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.JsonSyntaxException;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import org.jackhuang.hmcl.util.C;
import org.jackhuang.hmcl.api.HMCLog;
import org.jackhuang.hmcl.core.MCUtils;
import org.jackhuang.hmcl.core.download.DownloadType;
import org.jackhuang.hmcl.util.CollectionUtils;
import org.jackhuang.hmcl.api.HMCLApi;
import org.jackhuang.hmcl.api.event.config.ProfileChangedEvent;
import org.jackhuang.hmcl.api.event.config.ProfileLoadingEvent;
import org.jackhuang.hmcl.laf.utils.AnimationController;
import org.jackhuang.hmcl.util.sys.FileUtils;
import org.jackhuang.hmcl.util.ui.MessageBox;
import org.jackhuang.hmcl.util.upgrade.UpdateChecker;
import org.jackhuang.hmcl.util.sys.IOUtils;
import javafx.beans.binding.Bindings;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.HMCLCacheRepository;
import org.jackhuang.hmcl.util.CacheRepository;
import org.jackhuang.hmcl.util.io.FileUtils;
/**
*
* @author huangyuhui
*/
public final class Settings {
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
public static final String DEFAULT_PROFILE = "Default";
public static final String HOME_PROFILE = "Home";
public class Settings {
public static final File SETTINGS_FILE = new File("hmcl.json").getAbsoluteFile();
private static Settings instance;
private static final Config SETTINGS;
public static final UpdateChecker UPDATE_CHECKER = new UpdateChecker(HMCLApi.HMCL_VERSION, "hmcl");
public static Config getInstance() {
return SETTINGS;
}
static {
SETTINGS = initSettings();
DownloadType.setSuggestedDownloadType(SETTINGS.getDownloadSource().name());
AnimationController.ENABLE_ANIMATION = SETTINGS.isEnableAnimation();
if (!getProfiles().containsKey(DEFAULT_PROFILE))
getProfiles().put(DEFAULT_PROFILE, new Profile(DEFAULT_PROFILE));
for (Map.Entry<String, Profile> entry : getProfiles().entrySet()) {
Profile e = entry.getValue();
e.setName(entry.getKey());
e.propertyChanged.register(Settings::save);
public static Settings instance() {
if (instance == null) {
throw new IllegalStateException("Settings hasn't been initialized");
}
return instance;
}
private static Config initSettings() {
Config c = new Config();
if (SETTINGS_FILE.exists())
try {
String str = FileUtils.read(SETTINGS_FILE, IOUtils.DEFAULT_CHARSET);
if (str == null || str.trim().equals(""))
HMCLog.log("Settings file is empty, use the default settings.");
else {
Config d = C.GSON.fromJson(str, Config.class);
if (d != null)
c = d;
}
HMCLog.log("Initialized settings.");
} catch (IOException | JsonSyntaxException e) {
HMCLog.warn("Something happened wrongly when load settings.", e);
/**
* Should be called from {@link ConfigHolder#init()}.
*/
static void init() {
instance = new Settings();
}
private Settings() {
DownloadProviders.init();
ProxyManager.init();
Accounts.init();
Profiles.init();
CacheRepository.setInstance(HMCLCacheRepository.REPOSITORY);
HMCLCacheRepository.REPOSITORY.directoryProperty().bind(Bindings.createStringBinding(() -> {
if (FileUtils.canCreateDirectory(getCommonDirectory())) {
return getCommonDirectory();
} else {
return getDefaultCommonDirectory();
}
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()));
}, config().commonDirectoryProperty(), config().commonDirTypeProperty()));
}
public static String getDefaultCommonDirectory() {
return Metadata.MINECRAFT_DIRECTORY.toString();
}
public String getCommonDirectory() {
switch (config().getCommonDirType()) {
case DEFAULT:
return getDefaultCommonDirectory();
case CUSTOM:
return config().getCommonDirectory();
default:
return null;
}
return c;
}
public static void save() {
try {
FileUtils.write(SETTINGS_FILE, C.GSON.toJson(SETTINGS), IOUtils.DEFAULT_CHARSET);
} catch (IOException ex) {
HMCLog.err("Failed to save config", ex);
}
}
public static Profile getLastProfile() {
if (!hasProfile(getInstance().getLast()))
getInstance().setLast(DEFAULT_PROFILE);
return getProfile(getInstance().getLast());
}
public static Profile getProfile(String name) {
if (name == null)
name = DEFAULT_PROFILE;
Profile p = getProfiles().get(name);
if (p == null)
if (getProfiles().containsKey(DEFAULT_PROFILE))
p = getProfiles().get(DEFAULT_PROFILE);
else
getProfiles().put(DEFAULT_PROFILE, p = new Profile());
return p;
}
public static boolean hasProfile(String name) {
if (name == null)
name = DEFAULT_PROFILE;
return getProfiles().containsKey(name);
}
public static Map<String, Profile> getProfiles() {
return SETTINGS.getConfigurations();
}
public static Collection<Profile> getProfilesFiltered() {
return CollectionUtils.filter(getProfiles().values(), t -> t != null && t.getName() != null);
}
public static boolean putProfile(Profile ver) {
if (ver == null || ver.getName() == null || getProfiles().containsKey(ver.getName()))
return false;
getProfiles().put(ver.getName(), ver);
return true;
}
public static boolean delProfile(Profile ver) {
return delProfile(ver.getName());
}
public static boolean delProfile(String ver) {
if (DEFAULT_PROFILE.equals(ver)) {
MessageBox.show(C.i18n("settings.cannot_remove_default_config"));
return false;
}
boolean notify = false;
if (getLastProfile().getName().equals(ver))
notify = true;
boolean flag = getProfiles().remove(ver) != null;
if (notify && flag)
onProfileChanged();
return flag;
}
static void onProfileChanged() {
Profile p = getLastProfile();
if (p == null)
throw new Error("No profiles here, it should not happen");
HMCLApi.EVENT_BUS.fireChannel(new ProfileChangedEvent(SETTINGS, p));
p.onSelected();
}
public static void onProfileLoading() {
HMCLApi.EVENT_BUS.fireChannel(new ProfileLoadingEvent(SETTINGS));
onProfileChanged();
}
}
@@ -0,0 +1,159 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.annotations.JsonAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.scene.paint.Color;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.logging.Level;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
@JsonAdapter(Theme.TypeAdapter.class)
public class Theme {
public static final Theme BLUE = new Theme("blue", "#5C6BC0");
public static final Color[] SUGGESTED_COLORS = new Color[]{
Color.web("#5C6BC0"), // blue
Color.web("#283593"), // dark blue
Color.web("#43A047"), // green
Color.web("#E67E22"), // orange
Color.web("#9C27B0"), // purple
Color.web("#B71C1C") // red
};
private final String color;
private final String name;
Theme(String name, String color) {
this.name = name;
this.color = color;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public boolean isCustom() {
return name.startsWith("#");
}
public boolean isLight() {
return Color.web(color).grayscale().getRed() >= 0.5;
}
public Color getForegroundColor() {
return isLight() ? Color.BLACK : Color.WHITE;
}
public String[] getStylesheets() {
String css;
try {
File temp = File.createTempFile("hmcl", ".css");
FileUtils.writeText(temp, IOUtils.readFullyAsString(Theme.class.getResourceAsStream("/assets/css/custom.css"))
.replace("%base-color%", color)
.replace("%font-color%", getColorDisplayName(getForegroundColor())));
css = temp.toURI().toString();
} catch (IOException | NullPointerException e) {
Logging.LOG.log(Level.SEVERE, "Unable to create theme stylesheet. Fallback to blue theme.", e);
css = "/assets/css/blue.css";
}
return new String[]{
css,
"/assets/css/root.css"
};
}
public static Theme custom(String color) {
if (!color.startsWith("#"))
throw new IllegalArgumentException();
return new Theme(color, color);
}
public static Optional<Theme> getTheme(String name) {
if (name == null)
return Optional.empty();
else if (name.equalsIgnoreCase("blue"))
return Optional.of(custom("#5C6BC0"));
else if (name.equalsIgnoreCase("darker_blue"))
return Optional.of(custom("#283593"));
else if (name.equalsIgnoreCase("green"))
return Optional.of(custom("#43A047"));
else if (name.equalsIgnoreCase("orange"))
return Optional.of(custom("#E67E22"));
else if (name.equalsIgnoreCase("purple"))
return Optional.of(custom("#9C27B0"));
else if (name.equalsIgnoreCase("red"))
return Optional.of(custom("#F44336"));
if (name.startsWith("#"))
try {
Color.web(name);
return Optional.of(custom(name));
} catch (IllegalArgumentException ignore) {
}
return Optional.empty();
}
public static String getColorDisplayName(Color c) {
return c != null ? String.format("#%02x%02x%02x", Math.round(c.getRed() * 255.0D), Math.round(c.getGreen() * 255.0D), Math.round(c.getBlue() * 255.0D)).toUpperCase() : null;
}
public static ObjectBinding<Color> foregroundFillBinding() {
return BindingMapping.of(config().themeProperty())
.map(Theme::getForegroundColor);
}
public static ObjectBinding<Color> blackFillBinding() {
return Bindings.createObjectBinding(() -> Color.BLACK);
}
public static ObjectBinding<Color> whiteFillBinding() {
return Bindings.createObjectBinding(() -> Color.WHITE);
}
public static class TypeAdapter extends com.google.gson.TypeAdapter<Theme> {
@Override
public void write(JsonWriter out, Theme value) throws IOException {
out.value(value.getName().toLowerCase());
}
@Override
public Theme read(JsonReader in) throws IOException {
return getTheme(in.nextString()).orElse(Theme.BLUE);
}
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,329 +13,634 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.setting;
import com.google.gson.annotations.SerializedName;
import com.google.gson.*;
import com.google.gson.annotations.JsonAdapter;
import javafx.beans.InvalidationListener;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.LaunchOptions;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
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 java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
/**
*
* @author huangyuhui
*/
public class VersionSetting {
@JsonAdapter(VersionSetting.Serializer.class)
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 BooleanProperty usesGlobalProperty = new SimpleBooleanProperty(this, "usesGlobal", false);
public BooleanProperty usesGlobalProperty() {
return usesGlobalProperty;
}
/**
* HMCL Version Settings have been divided into 2 parts.
* 1. Global settings.
* 2. Version settings.
* If a version claims that it uses global settings, its version setting will be disabled.
*
* Defaults false because if one version uses global first, custom version file will not be generated.
*/
public boolean isUsesGlobal() {
return usesGlobalProperty.get();
}
public void setUsesGlobal(boolean usesGlobal) {
usesGlobalProperty.set(usesGlobal);
}
// java
private final StringProperty javaProperty = new SimpleStringProperty(this, "java", "");
public StringProperty javaProperty() {
return javaProperty;
}
/**
* Java version or "Custom" if user customizes java directory, "Default" if the jvm that this app relies on.
*/
public String getJava() {
return javaProperty.get();
}
public void setJava(String java) {
javaProperty.set(java);
}
public boolean isUsesCustomJavaDir() {
return "Custom".equals(getJava());
}
public void setUsesCustomJavaDir() {
setJava("Custom");
setDefaultJavaPath(null);
}
private final StringProperty defaultJavaPathProperty = new SimpleStringProperty(this, "defaultJavaPath", "");
/**
* Path to Java executable, or null if user customizes java directory.
* It's used to determine which JRE to use when multiple JREs match the selected Java version.
*/
public String getDefaultJavaPath() {
return defaultJavaPathProperty.get();
}
public void setDefaultJavaPath(String defaultJavaPath) {
defaultJavaPathProperty.set(defaultJavaPath);
}
private final StringProperty javaDirProperty = new SimpleStringProperty(this, "javaDir", "");
public StringProperty javaDirProperty() {
return javaDirProperty;
}
/**
* User customized java directory or null if user uses system Java.
*/
public String getJavaDir() {
return javaDirProperty.get();
}
public void setJavaDir(String javaDir) {
javaDirProperty.set(javaDir);
}
private final StringProperty wrapperProperty = new SimpleStringProperty(this, "wrapper", "");
public StringProperty wrapperProperty() {
return wrapperProperty;
}
/**
* The command to launch java, i.e. optirun.
*/
public String getWrapper() {
return wrapperProperty.get();
}
public void setWrapper(String wrapper) {
wrapperProperty.set(wrapper);
}
private final StringProperty permSizeProperty = new SimpleStringProperty(this, "permSize", "");
public StringProperty permSizeProperty() {
return permSizeProperty;
}
/**
* The permanent generation size of JVM garbage collection.
*/
public String getPermSize() {
return permSizeProperty.get();
}
public void setPermSize(String permSize) {
permSizeProperty.set(permSize);
}
private final IntegerProperty maxMemoryProperty = new SimpleIntegerProperty(this, "maxMemory", OperatingSystem.SUGGESTED_MEMORY);
public IntegerProperty maxMemoryProperty() {
return maxMemoryProperty;
}
/**
* The maximum memory/MB that JVM can allocate for heap.
*/
public int getMaxMemory() {
return maxMemoryProperty.get();
}
public void setMaxMemory(int maxMemory) {
maxMemoryProperty.set(maxMemory);
}
/**
* The minimum memory that JVM can allocate for heap.
*/
private final ObjectProperty<Integer> minMemoryProperty = new SimpleObjectProperty<>(this, "minMemory", null);
public ObjectProperty<Integer> minMemoryProperty() {
return minMemoryProperty;
}
public Integer getMinMemory() {
return minMemoryProperty.get();
}
public void setMinMemory(Integer minMemory) {
minMemoryProperty.set(minMemory);
}
private final StringProperty preLaunchCommandProperty = new SimpleStringProperty(this, "precalledCommand", "");
public StringProperty preLaunchCommandProperty() {
return preLaunchCommandProperty;
}
/**
* The command that will be executed before launching the Minecraft.
* Operating system relevant.
*/
public String getPreLaunchCommand() {
return preLaunchCommandProperty.get();
}
public void setPreLaunchCommand(String preLaunchCommand) {
preLaunchCommandProperty.set(preLaunchCommand);
}
// options
private final StringProperty javaArgsProperty = new SimpleStringProperty(this, "javaArgs", "");
public StringProperty javaArgsProperty() {
return javaArgsProperty;
}
/**
* The user customized arguments passed to JVM.
*/
public String getJavaArgs() {
return javaArgsProperty.get();
}
public void setJavaArgs(String javaArgs) {
javaArgsProperty.set(javaArgs);
}
private final StringProperty minecraftArgsProperty = new SimpleStringProperty(this, "minecraftArgs", "");
public StringProperty minecraftArgsProperty() {
return minecraftArgsProperty;
}
/**
* The user customized arguments passed to Minecraft.
*/
public String getMinecraftArgs() {
return minecraftArgsProperty.get();
}
public void setMinecraftArgs(String minecraftArgs) {
minecraftArgsProperty.set(minecraftArgs);
}
private final BooleanProperty noJVMArgsProperty = new SimpleBooleanProperty(this, "noJVMArgs", false);
public BooleanProperty noJVMArgsProperty() {
return noJVMArgsProperty;
}
/**
* True if disallow HMCL use default JVM arguments.
*/
public boolean isNoJVMArgs() {
return noJVMArgsProperty.get();
}
public void setNoJVMArgs(boolean noJVMArgs) {
noJVMArgsProperty.set(noJVMArgs);
}
private final BooleanProperty notCheckJVMProperty = new SimpleBooleanProperty(this, "notCheckJVM", false);
public BooleanProperty notCheckJVMProperty() {
return notCheckJVMProperty;
}
/**
* True if HMCL does not check JVM validity.
*/
public boolean isNotCheckJVM() {
return notCheckJVMProperty.get();
}
public void setNotCheckJVM(boolean notCheckJVM) {
notCheckJVMProperty.set(notCheckJVM);
}
private final BooleanProperty notCheckGameProperty = new SimpleBooleanProperty(this, "notCheckGame", false);
public BooleanProperty notCheckGameProperty() {
return notCheckGameProperty;
}
/**
* True if HMCL does not check game's completeness.
*/
public boolean isNotCheckGame() {
return notCheckGameProperty.get();
}
public void setNotCheckGame(boolean notCheckGame) {
notCheckGameProperty.set(notCheckGame);
}
private final BooleanProperty showLogsProperty = new SimpleBooleanProperty(this, "showLogs", false);
public BooleanProperty showLogsProperty() {
return showLogsProperty;
}
/**
* True if show the logs after game launched.
*/
public boolean isShowLogs() {
return showLogsProperty.get();
}
public void setShowLogs(boolean showLogs) {
showLogsProperty.set(showLogs);
}
// Minecraft settings.
private final StringProperty serverIpProperty = new SimpleStringProperty(this, "serverIp", "");
public StringProperty serverIpProperty() {
return serverIpProperty;
}
/**
* The server ip that will be entered after Minecraft successfully loaded ly.
*
* Format: ip:port or without port.
*/
public String getServerIp() {
return serverIpProperty.get();
}
public void setServerIp(String serverIp) {
serverIpProperty.set(serverIp);
}
private final BooleanProperty fullscreenProperty = new SimpleBooleanProperty(this, "fullscreen", false);
public BooleanProperty fullscreenProperty() {
return fullscreenProperty;
}
/**
* True if Minecraft started in fullscreen mode.
*/
public boolean isFullscreen() {
return fullscreenProperty.get();
}
public void setFullscreen(boolean fullscreen) {
fullscreenProperty.set(fullscreen);
}
private final IntegerProperty widthProperty = new SimpleIntegerProperty(this, "width", 854);
public IntegerProperty widthProperty() {
return widthProperty;
}
/**
* The width of Minecraft window, defaults 800.
*
* The field saves int value.
* String type prevents unexpected value from JsonParseException.
* We can only reset this field instead of recreating the whole setting file.
*/
public int getWidth() {
return widthProperty.get();
}
public void setWidth(int width) {
widthProperty.set(width);
}
private final IntegerProperty heightProperty = new SimpleIntegerProperty(this, "height", 480);
public IntegerProperty heightProperty() {
return heightProperty;
}
/**
* The height of Minecraft window, defaults 480.
*
* The field saves int value.
* String type prevents unexpected value from JsonParseException.
* We can only reset this field instead of recreating the whole setting file.
*/
public int getHeight() {
return heightProperty.get();
}
public void setHeight(int height) {
heightProperty.set(height);
}
/**
* 0 - .minecraft<br/>
* 1 - .minecraft/versions/&lt;version&gt;/<br/>
*/
private final ObjectProperty<EnumGameDirectory> gameDirTypeProperty = new SimpleObjectProperty<>(this, "gameDirType", EnumGameDirectory.ROOT_FOLDER);
public ObjectProperty<EnumGameDirectory> gameDirTypeProperty() {
return gameDirTypeProperty;
}
public EnumGameDirectory getGameDirType() {
return gameDirTypeProperty.get();
}
public void setGameDirType(EnumGameDirectory gameDirType) {
gameDirTypeProperty.set(gameDirType);
}
/**
* Your custom gameDir
*/
private final StringProperty gameDirProperty = new SimpleStringProperty(this, "gameDir", "");
public StringProperty gameDirProperty() {
return gameDirProperty;
}
public String getGameDir() {
return gameDirProperty.get();
}
public void setGameDir(String gameDir) {
gameDirProperty.set(gameDir);
}
// launcher settings
/**
* 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 ObjectProperty<LauncherVisibility> launcherVisibilityProperty = new SimpleObjectProperty<>(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 ObjectProperty<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 (isUsesCustomJavaDir()) {
try {
return JavaVersion.fromExecutable(Paths.get(getJavaDir()));
} catch (IOException e) {
return null; // Custom Java Directory not found,
}
} else if (StringUtils.isNotBlank(getJava())) {
List<JavaVersion> matchedJava = JavaVersion.getJavas().stream()
.filter(java -> java.getVersion().equals(getJava()))
.collect(Collectors.toList());
if (matchedJava.isEmpty()) {
setJava("Default");
return JavaVersion.fromCurrentEnvironment();
} else {
return matchedJava.stream()
.filter(java -> java.getBinary().toString().equals(getDefaultJavaPath()))
.findFirst()
.orElse(matchedJava.get(0));
}
} else throw new Error();
}
public void setGameDirType(GameDirType gameDirType) {
PropertyChangedEvent event = new PropertyChangedEvent(this, "gameDirType", this.gameDirType, gameDirType);
this.gameDirType = gameDirType.ordinal();
propertyChanged.fire(event);
public void setJavaVersion(JavaVersion java) {
setJava(java.getVersion());
setDefaultJavaPath(java.getBinary().toString());
}
public String getPermSize() {
return permSize;
public void addPropertyChangedListener(InvalidationListener listener) {
usesGlobalProperty.addListener(listener);
javaProperty.addListener(listener);
javaDirProperty.addListener(listener);
wrapperProperty.addListener(listener);
permSizeProperty.addListener(listener);
maxMemoryProperty.addListener(listener);
minMemoryProperty.addListener(listener);
preLaunchCommandProperty.addListener(listener);
javaArgsProperty.addListener(listener);
minecraftArgsProperty.addListener(listener);
noJVMArgsProperty.addListener(listener);
notCheckGameProperty.addListener(listener);
notCheckJVMProperty.addListener(listener);
showLogsProperty.addListener(listener);
serverIpProperty.addListener(listener);
fullscreenProperty.addListener(listener);
widthProperty.addListener(listener);
heightProperty.addListener(listener);
gameDirTypeProperty.addListener(listener);
gameDirProperty.addListener(listener);
launcherVisibilityProperty.addListener(listener);
defaultJavaPathProperty.addListener(listener);
}
public void setPermSize(String permSize) {
PropertyChangedEvent event = new PropertyChangedEvent(this, "permSize", this.permSize, permSize);
this.permSize = permSize;
propertyChanged.fire(event);
}
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();
public LaunchOptions toLaunchOptions(File gameDir) throws InterruptedException {
JavaVersion javaVersion = Optional.ofNullable(getJavaVersion()).orElse(JavaVersion.fromCurrentEnvironment());
LaunchOptions.Builder builder = new LaunchOptions.Builder()
.setGameDir(gameDir)
.setJava(javaVersion)
.setVersionName(Metadata.TITLE)
.setProfileName(Metadata.TITLE)
.setMinecraftArgs(getMinecraftArgs())
.setJavaArgs(getJavaArgs())
.setMaxMemory(getMaxMemory())
.setMinMemory(getMinMemory())
.setMetaspace(Lang.toIntOrNull(getPermSize()))
.setWidth(getWidth())
.setHeight(getHeight())
.setFullscreen(isFullscreen())
.setServerIp(getServerIp())
.setWrapper(getWrapper())
.setPrecalledCommand(getPreLaunchCommand())
.setNoGeneratedJVMArgs(isNoJVMArgs());
if (config().hasProxy()) {
builder.setProxyHost(config().getProxyHost());
builder.setProxyPort(config().getProxyPort());
if (config().hasProxyAuth()) {
builder.setProxyUser(config().getProxyUser());
builder.setProxyPass(config().getProxyPass());
}
}
return builder.create();
}
public static class Serializer implements JsonSerializer<VersionSetting>, JsonDeserializer<VersionSetting> {
@Override
public JsonElement serialize(VersionSetting src, Type typeOfSrc, JsonSerializationContext context) {
if (src == null) return JsonNull.INSTANCE;
JsonObject obj = new JsonObject();
obj.addProperty("usesGlobal", src.isUsesGlobal());
obj.addProperty("javaArgs", src.getJavaArgs());
obj.addProperty("minecraftArgs", src.getMinecraftArgs());
obj.addProperty("maxMemory", src.getMaxMemory() <= 0 ? OperatingSystem.SUGGESTED_MEMORY : src.getMaxMemory());
obj.addProperty("minMemory", src.getMinMemory());
obj.addProperty("permSize", src.getPermSize());
obj.addProperty("width", src.getWidth());
obj.addProperty("height", src.getHeight());
obj.addProperty("javaDir", src.getJavaDir());
obj.addProperty("precalledCommand", src.getPreLaunchCommand());
obj.addProperty("serverIp", src.getServerIp());
obj.addProperty("java", src.getJava());
obj.addProperty("wrapper", src.getWrapper());
obj.addProperty("fullscreen", src.isFullscreen());
obj.addProperty("noJVMArgs", src.isNoJVMArgs());
obj.addProperty("notCheckGame", src.isNotCheckGame());
obj.addProperty("notCheckJVM", src.isNotCheckJVM());
obj.addProperty("showLogs", src.isShowLogs());
obj.addProperty("gameDir", src.getGameDir());
obj.addProperty("launcherVisibility", src.getLauncherVisibility().ordinal());
obj.addProperty("gameDirType", src.getGameDirType().ordinal());
obj.addProperty("defaultJavaPath", src.getDefaultJavaPath());
return obj;
}
@Override
public VersionSetting deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json == null || json == JsonNull.INSTANCE || !(json instanceof JsonObject))
return null;
JsonObject obj = (JsonObject) json;
int maxMemoryN = parseJsonPrimitive(Optional.ofNullable(obj.get("maxMemory")).map(JsonElement::getAsJsonPrimitive).orElse(null), OperatingSystem.SUGGESTED_MEMORY);
if (maxMemoryN <= 0) maxMemoryN = OperatingSystem.SUGGESTED_MEMORY;
VersionSetting vs = new VersionSetting();
vs.setUsesGlobal(Optional.ofNullable(obj.get("usesGlobal")).map(JsonElement::getAsBoolean).orElse(false));
vs.setJavaArgs(Optional.ofNullable(obj.get("javaArgs")).map(JsonElement::getAsString).orElse(""));
vs.setMinecraftArgs(Optional.ofNullable(obj.get("minecraftArgs")).map(JsonElement::getAsString).orElse(""));
vs.setMaxMemory(maxMemoryN);
vs.setMinMemory(Optional.ofNullable(obj.get("minMemory")).map(JsonElement::getAsInt).orElse(null));
vs.setPermSize(Optional.ofNullable(obj.get("permSize")).map(JsonElement::getAsString).orElse(""));
vs.setWidth(Optional.ofNullable(obj.get("width")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0));
vs.setHeight(Optional.ofNullable(obj.get("height")).map(JsonElement::getAsJsonPrimitive).map(this::parseJsonPrimitive).orElse(0));
vs.setJavaDir(Optional.ofNullable(obj.get("javaDir")).map(JsonElement::getAsString).orElse(""));
vs.setPreLaunchCommand(Optional.ofNullable(obj.get("precalledCommand")).map(JsonElement::getAsString).orElse(""));
vs.setServerIp(Optional.ofNullable(obj.get("serverIp")).map(JsonElement::getAsString).orElse(""));
vs.setJava(Optional.ofNullable(obj.get("java")).map(JsonElement::getAsString).orElse(""));
vs.setWrapper(Optional.ofNullable(obj.get("wrapper")).map(JsonElement::getAsString).orElse(""));
vs.setGameDir(Optional.ofNullable(obj.get("gameDir")).map(JsonElement::getAsString).orElse(""));
vs.setFullscreen(Optional.ofNullable(obj.get("fullscreen")).map(JsonElement::getAsBoolean).orElse(false));
vs.setNoJVMArgs(Optional.ofNullable(obj.get("noJVMArgs")).map(JsonElement::getAsBoolean).orElse(false));
vs.setNotCheckGame(Optional.ofNullable(obj.get("notCheckGame")).map(JsonElement::getAsBoolean).orElse(false));
vs.setNotCheckJVM(Optional.ofNullable(obj.get("notCheckJVM")).map(JsonElement::getAsBoolean).orElse(false));
vs.setShowLogs(Optional.ofNullable(obj.get("showLogs")).map(JsonElement::getAsBoolean).orElse(false));
vs.setLauncherVisibility(LauncherVisibility.values()[Optional.ofNullable(obj.get("launcherVisibility")).map(JsonElement::getAsInt).orElse(1)]);
vs.setGameDirType(EnumGameDirectory.values()[Optional.ofNullable(obj.get("gameDirType")).map(JsonElement::getAsInt).orElse(0)]);
vs.setDefaultJavaPath(Optional.ofNullable(obj.get("defaultJavaPath")).map(JsonElement::getAsString).orElse(null));
return vs;
}
private int parseJsonPrimitive(JsonPrimitive primitive) {
return parseJsonPrimitive(primitive, 0);
}
private int parseJsonPrimitive(JsonPrimitive primitive, int defaultValue) {
if (primitive == null)
return defaultValue;
else if (primitive.isNumber())
return primitive.getAsInt();
else
return Lang.parseInt(primitive.getAsString(), defaultValue);
}
x.setJavaDir(str);
return x;
}
}
@@ -0,0 +1,285 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.utils.JFXUtilities;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import org.jackhuang.hmcl.Launcher;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.Version;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.account.AccountList;
import org.jackhuang.hmcl.ui.account.AuthlibInjectorServersPage;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.decorator.DecoratorController;
import org.jackhuang.hmcl.ui.download.ModpackInstallWizardProvider;
import org.jackhuang.hmcl.ui.profile.ProfileList;
import org.jackhuang.hmcl.ui.versions.GameItem;
import org.jackhuang.hmcl.ui.versions.GameList;
import org.jackhuang.hmcl.ui.versions.VersionPage;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.util.FutureCallback;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import org.jackhuang.hmcl.util.platform.JavaVersion;
import org.jackhuang.hmcl.util.versioning.VersionNumber;
import java.io.File;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class Controllers {
private static Scene scene;
private static Stage stage;
private static MainPage mainPage = null;
private static SettingsPage settingsPage = null;
private static VersionPage versionPage = null;
private static GameList gameListPage = null;
private static AccountList accountListPage = null;
private static ProfileList profileListPage = null;
private static AuthlibInjectorServersPage serversPage = null;
private static LeftPaneController leftPaneController;
private static DecoratorController decorator;
public static Scene getScene() {
return scene;
}
public static Stage getStage() {
return stage;
}
// FXThread
public static SettingsPage getSettingsPage() {
if (settingsPage == null)
settingsPage = new SettingsPage();
return settingsPage;
}
// FXThread
public static GameList getGameListPage() {
if (gameListPage == null) {
gameListPage = new GameList();
FXUtils.applyDragListener(gameListPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
File modpack = modpacks.get(0);
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
});
}
return gameListPage;
}
// FXThread
public static AccountList getAccountListPage() {
if (accountListPage == null) {
AccountList accountListPage = new AccountList();
accountListPage.selectedAccountProperty().bindBidirectional(Accounts.selectedAccountProperty());
accountListPage.accountsProperty().bindContent(Accounts.accountsProperty());
Controllers.accountListPage = accountListPage;
}
return accountListPage;
}
// FXThread
public static ProfileList getProfileListPage() {
if (profileListPage == null) {
ProfileList profileListPage = new ProfileList();
profileListPage.selectedProfileProperty().bindBidirectional(Profiles.selectedProfileProperty());
profileListPage.profilesProperty().bindContent(Profiles.profilesProperty());
Controllers.profileListPage = profileListPage;
}
return profileListPage;
}
// FXThread
public static VersionPage getVersionPage() {
if (versionPage == null)
versionPage = new VersionPage();
return versionPage;
}
// FXThread
public static AuthlibInjectorServersPage getServersPage() {
if (serversPage == null)
serversPage = new AuthlibInjectorServersPage();
return serversPage;
}
// FXThread
public static DecoratorController getDecorator() {
return decorator;
}
public static MainPage getMainPage() {
if (mainPage == null) {
MainPage mainPage = new MainPage();
FXUtils.applyDragListener(mainPage, it -> "zip".equals(FileUtils.getExtension(it)), modpacks -> {
File modpack = modpacks.get(0);
Controllers.getDecorator().startWizard(new ModpackInstallWizardProvider(Profiles.getSelectedProfile(), modpack), i18n("install.modpack"));
});
FXUtils.onChangeAndOperate(Profiles.selectedVersionProperty(), version -> {
if (version != null) {
mainPage.setCurrentGame(version);
} else {
mainPage.setCurrentGame(i18n("version.empty"));
}
});
mainPage.showUpdateProperty().bind(UpdateChecker.outdatedProperty());
mainPage.latestVersionProperty().bind(
BindingMapping.of(UpdateChecker.latestVersionProperty())
.map(version -> version == null ? "" : i18n("update.bubble.title", version.getVersion())));
Profiles.registerVersionsListener(profile -> {
HMCLGameRepository repository = profile.getRepository();
List<Node> children = repository.getVersions().parallelStream()
.filter(version -> !version.isHidden())
.sorted(Comparator.comparing((Version version) -> version.getReleaseTime() == null ? new Date(0L) : version.getReleaseTime())
.thenComparing(a -> VersionNumber.asVersion(a.getId())))
.map(version -> {
Node node = PopupMenu.wrapPopupMenuItem(new GameItem(profile, version.getId()));
node.setOnMouseClicked(e -> profile.setSelectedVersion(version.getId()));
return node;
})
.collect(Collectors.toList());
JFXUtilities.runInFX(() -> {
if (profile == Profiles.getSelectedProfile())
mainPage.getVersions().setAll(children);
});
});
Controllers.mainPage = mainPage;
}
return mainPage;
}
public static LeftPaneController getLeftPaneController() {
return leftPaneController;
}
public static void initialize(Stage stage) {
Logging.LOG.info("Start initializing application");
Controllers.stage = stage;
stage.setOnCloseRequest(e -> Launcher.stopApplication());
decorator = new DecoratorController(stage, getMainPage());
leftPaneController = new LeftPaneController();
decorator.getDecorator().drawerProperty().setAll(leftPaneController);
if (config().getCommonDirType() == EnumCommonDirectory.CUSTOM &&
!FileUtils.canCreateDirectory(config().getCommonDirectory())) {
config().setCommonDirType(EnumCommonDirectory.DEFAULT);
dialog(i18n("launcher.cache_directory.invalid"));
}
Task.runAsync(JavaVersion::initialize).start();
scene = new Scene(decorator.getDecorator(), 800, 519);
scene.getStylesheets().setAll(config().getTheme().getStylesheets());
stage.getIcons().add(new Image("/assets/img/icon.png"));
stage.setTitle(Metadata.TITLE);
}
public static void dialog(Region content) {
if (decorator != null)
decorator.showDialog(content);
}
public static void dialog(String text) {
dialog(text, null);
}
public static void dialog(String text, String title) {
dialog(text, title, MessageType.INFORMATION);
}
public static void dialog(String text, String title, MessageType type) {
dialog(text, title, type, null);
}
public static void dialog(String text, String title, MessageType type, Runnable onAccept) {
dialog(new MessageDialogPane(text, title, type, onAccept));
}
public static void confirmDialog(String text, String title, Runnable onAccept, Runnable onCancel) {
dialog(new MessageDialogPane(text, title, onAccept, onCancel));
}
public static InputDialogPane inputDialog(String text, FutureCallback<String> onResult) {
InputDialogPane pane = new InputDialogPane(text, onResult);
dialog(pane);
return pane;
}
public static Region taskDialog(TaskExecutor executor, String title) {
return taskDialog(executor, title, "");
}
public static Region taskDialog(TaskExecutor executor, String title, String subtitle) {
return taskDialog(executor, title, subtitle, null);
}
public static Region taskDialog(TaskExecutor executor, String title, String subtitle, Consumer<Region> onCancel) {
TaskExecutorDialogPane pane = new TaskExecutorDialogPane(onCancel);
pane.setTitle(title);
pane.setSubtitle(subtitle);
pane.setExecutor(executor);
dialog(pane);
return pane;
}
public static void navigate(Node node) {
decorator.getNavigator().navigate(node);
}
public static boolean isStopped() {
return decorator == null;
}
public static void shutdown() {
mainPage = null;
settingsPage = null;
versionPage = null;
serversPage = null;
decorator = null;
stage = null;
scene = null;
gameListPage = null;
accountListPage = null;
profileListPage = null;
}
}
@@ -0,0 +1,76 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.image.Image;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
* @author huangyuhui
*/
public class CrashWindow extends Stage {
public CrashWindow(String text) {
Label lblCrash = new Label();
if (UpdateChecker.isOutdated())
lblCrash.setText(i18n("launcher.crash_out_dated"));
else
lblCrash.setText(i18n("launcher.crash"));
lblCrash.setWrapText(true);
TextArea textArea = new TextArea();
textArea.setText(text);
textArea.setEditable(false);
Button btnContact = new Button();
btnContact.setText(i18n("launcher.contact"));
btnContact.setOnMouseClicked(event -> FXUtils.openLink(Metadata.CONTACT_URL));
HBox box = new HBox();
box.setStyle("-fx-padding: 8px;");
box.getChildren().add(btnContact);
box.setAlignment(Pos.CENTER_RIGHT);
BorderPane pane = new BorderPane();
StackPane stackPane = new StackPane();
stackPane.setStyle("-fx-padding: 8px;");
stackPane.getChildren().add(lblCrash);
pane.setTop(stackPane);
pane.setCenter(textArea);
pane.setBottom(box);
Scene scene = new Scene(pane, 800, 480);
setScene(scene);
getIcons().add(new Image("/assets/img/icon.png"));
setTitle(i18n("message.error"));
setOnCloseRequest(e -> System.exit(1));
}
}
@@ -0,0 +1,50 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.utils.JFXUtilities;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.ui.account.AccountLoginPane;
import java.util.Optional;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
public final class DialogController {
public static AuthInfo logIn(Account account) throws CancellationException, AuthenticationException, InterruptedException {
if (account instanceof YggdrasilAccount) {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<AuthInfo> res = new AtomicReference<>(null);
JFXUtilities.runInFX(() -> {
AccountLoginPane pane = new AccountLoginPane(account, it -> {
res.set(it);
latch.countDown();
}, latch::countDown);
Controllers.dialog(pane);
});
latch.await();
return Optional.ofNullable(res.get()).orElseThrow(CancellationException::new);
}
return account.logIn();
}
}
@@ -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,539 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.utils.JFXUtilities;
import com.jfoenix.controls.*;
import javafx.animation.Animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.util.Callback;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.jackhuang.hmcl.util.*;
import org.jackhuang.hmcl.util.i18n.I18n;
import org.jackhuang.hmcl.util.io.FileUtils;
import org.jackhuang.hmcl.util.javafx.ExtendedProperties;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import org.jackhuang.hmcl.util.platform.OperatingSystem;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.stream.Collectors;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Lang.tryCast;
public final class FXUtils {
private FXUtils() {
}
public static void checkFxUserThread() {
if (!Platform.isFxApplicationThread()) {
throw new IllegalStateException("Not on FX application thread; currentThread = "
+ Thread.currentThread().getName());
}
}
public static InvalidationListener onInvalidating(Runnable action) {
return arg -> action.run();
}
public static <T> void onChange(ObservableValue<T> value, Consumer<T> consumer) {
value.addListener((a, b, c) -> consumer.accept(c));
}
public static <T> void onWeakChange(ObservableValue<T> value, Consumer<T> consumer) {
value.addListener(new WeakChangeListener<>((a, b, c) -> consumer.accept(c)));
}
public static <T> void onChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
consumer.accept(value.getValue());
onChange(value, consumer);
}
public static <T> void onWeakChangeAndOperate(ObservableValue<T> value, Consumer<T> consumer) {
consumer.accept(value.getValue());
onWeakChange(value, consumer);
}
public static void runLaterIf(BooleanSupplier condition, Runnable runnable) {
if (condition.getAsBoolean()) Platform.runLater(() -> runLaterIf(condition, runnable));
else runnable.run();
}
public static void limitSize(ImageView imageView, double maxWidth, double maxHeight) {
imageView.setPreserveRatio(true);
onChangeAndOperate(imageView.imageProperty(), image -> {
if (image != null && (image.getWidth() > maxWidth || image.getHeight() > maxHeight)) {
imageView.setFitHeight(maxHeight);
imageView.setFitWidth(maxWidth);
} else {
imageView.setFitHeight(-1);
imageView.setFitWidth(-1);
}
});
}
private static class ListenerPair<T> {
private final ObservableValue<T> value;
private final ChangeListener<? super T> listener;
ListenerPair(ObservableValue<T> value, ChangeListener<? super T> listener) {
this.value = value;
this.listener = listener;
}
void bind() {
value.addListener(listener);
}
void unbind() {
value.removeListener(listener);
}
}
public static <T> void addListener(Node node, String key, ObservableValue<T> value, Consumer<? super T> callback) {
ListenerPair<T> pair = new ListenerPair<>(value, (a, b, newValue) -> callback.accept(newValue));
node.getProperties().put(key, pair);
pair.bind();
}
public static void removeListener(Node node, String key) {
tryCast(node.getProperties().get(key), ListenerPair.class)
.ifPresent(info -> {
info.unbind();
node.getProperties().remove(key);
});
}
public static void setValidateWhileTextChanged(Node field, boolean validate) {
if (field instanceof JFXTextField) {
if (validate) {
addListener(field, "FXUtils.validation", ((JFXTextField) field).textProperty(), o -> ((JFXTextField) field).validate());
} else {
removeListener(field, "FXUtils.validation");
}
((JFXTextField) field).validate();
} else if (field instanceof JFXPasswordField) {
if (validate) {
addListener(field, "FXUtils.validation", ((JFXPasswordField) field).textProperty(), o -> ((JFXPasswordField) field).validate());
} else {
removeListener(field, "FXUtils.validation");
}
((JFXPasswordField) field).validate();
} else
throw new IllegalArgumentException("Only JFXTextField and JFXPasswordField allowed");
}
public static boolean getValidateWhileTextChanged(Node field) {
return field.getProperties().containsKey("FXUtils.validation");
}
public static void setOverflowHidden(Region region, boolean hidden) {
if (hidden) {
Rectangle rectangle = new Rectangle();
rectangle.widthProperty().bind(region.widthProperty());
rectangle.heightProperty().bind(region.heightProperty());
region.setClip(rectangle);
} else {
region.setClip(null);
}
}
public static boolean getOverflowHidden(Region region) {
return region.getClip() != null;
}
public static void setLimitWidth(Region region, double width) {
region.setMaxWidth(width);
region.setMinWidth(width);
region.setPrefWidth(width);
}
public static double getLimitWidth(Region region) {
return region.getMaxWidth();
}
public static void setLimitHeight(Region region, double height) {
region.setMaxHeight(height);
region.setMinHeight(height);
region.setPrefHeight(height);
}
public static double getLimitHeight(Region region) {
return region.getMaxHeight();
}
public static Node limitingSize(Node node, double width, double height) {
StackPane pane = new StackPane(node);
pane.setAlignment(Pos.CENTER);
FXUtils.setLimitWidth(pane, width);
FXUtils.setLimitHeight(pane, height);
return pane;
}
public static void smoothScrolling(ScrollPane scrollPane) {
JFXScrollPane.smoothScrolling(scrollPane);
}
public static void loadFXML(Node node, String absolutePath) {
FXMLLoader loader = new FXMLLoader(node.getClass().getResource(absolutePath), I18n.getResourceBundle());
loader.setRoot(node);
loader.setController(node);
try {
loader.load();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static void installFastTooltip(Node node, Tooltip tooltip) {
installTooltip(node, 50, 5000, 0, tooltip);
}
public static void installFastTooltip(Node node, String tooltip) {
installFastTooltip(node, new Tooltip(tooltip));
}
public static void installSlowTooltip(Node node, Tooltip tooltip) {
installTooltip(node, 500, 5000, 0, tooltip);
}
public static void installSlowTooltip(Node node, String tooltip) {
installSlowTooltip(node, new Tooltip(tooltip));
}
public static void installTooltip(Node node, double openDelay, double visibleDelay, double closeDelay, Tooltip tooltip) {
JFXUtilities.runInFX(() -> {
try {
// Java 8
Class<?> behaviorClass = Class.forName("javafx.scene.control.Tooltip$TooltipBehavior");
Constructor<?> behaviorConstructor = behaviorClass.getDeclaredConstructor(Duration.class, Duration.class, Duration.class, boolean.class);
behaviorConstructor.setAccessible(true);
Object behavior = behaviorConstructor.newInstance(new Duration(openDelay), new Duration(visibleDelay), new Duration(closeDelay), false);
Method installMethod = behaviorClass.getDeclaredMethod("install", Node.class, Tooltip.class);
installMethod.setAccessible(true);
installMethod.invoke(behavior, node, tooltip);
} catch (ReflectiveOperationException e) {
try {
// Java 9
Tooltip.class.getMethod("setShowDelay", Duration.class).invoke(tooltip, new Duration(openDelay));
Tooltip.class.getMethod("setShowDuration", Duration.class).invoke(tooltip, new Duration(visibleDelay));
Tooltip.class.getMethod("setHideDelay", Duration.class).invoke(tooltip, new Duration(closeDelay));
} catch (ReflectiveOperationException e2) {
e.addSuppressed(e2);
Logging.LOG.log(Level.SEVERE, "Cannot install tooltip", e);
}
Tooltip.install(node, tooltip);
}
});
}
public static void openFolder(File file) {
if (!FileUtils.makeDirectory(file)) {
Logging.LOG.log(Level.SEVERE, "Unable to make directory " + file);
return;
}
String path = file.getAbsolutePath();
switch (OperatingSystem.CURRENT_OS) {
case OSX:
try {
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", path});
} catch (IOException e) {
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by executing /usr/bin/open", e);
}
break;
default:
thread(() -> {
if (java.awt.Desktop.isDesktopSupported()) {
try {
java.awt.Desktop.getDesktop().open(file);
} catch (Throwable e) {
Logging.LOG.log(Level.SEVERE, "Unable to open " + path + " by java.awt.Desktop.getDesktop()::open", e);
}
}
});
}
}
/**
* Open URL by java.awt.Desktop
*
* @param link null is allowed but will be ignored
*/
public static void openLink(String link) {
if (link == null)
return;
thread(() -> {
if (java.awt.Desktop.isDesktopSupported()) {
try {
java.awt.Desktop.getDesktop().browse(new URI(link));
} catch (Throwable e) {
if (OperatingSystem.CURRENT_OS == OperatingSystem.OSX)
try {
Runtime.getRuntime().exec(new String[]{"/usr/bin/open", link});
} catch (IOException ex) {
Logging.LOG.log(Level.WARNING, "Unable to open link: " + link, ex);
}
Logging.LOG.log(Level.WARNING, "Failed to open link: " + link, e);
}
}
});
}
public static void bindInt(JFXTextField textField, Property<Number> property) {
textField.textProperty().bindBidirectional(property, SafeStringConverter.fromInteger());
}
public static void unbindInt(JFXTextField textField, Property<Number> property) {
textField.textProperty().unbindBidirectional(property);
}
public static void bindString(JFXTextField textField, Property<String> property) {
textField.textProperty().bindBidirectional(property);
}
public static void unbindString(JFXTextField textField, Property<String> property) {
textField.textProperty().unbindBidirectional(property);
}
public static void bindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
toggleButton.selectedProperty().bindBidirectional(property);
}
public static void unbindBoolean(JFXToggleButton toggleButton, Property<Boolean> property) {
toggleButton.selectedProperty().unbindBidirectional(property);
}
public static void bindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
checkBox.selectedProperty().bindBidirectional(property);
}
public static void unbindBoolean(JFXCheckBox checkBox, Property<Boolean> property) {
checkBox.selectedProperty().unbindBidirectional(property);
}
/**
* Bind combo box selection with given enum property bidirectionally.
* You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.
* @param comboBox the combo box being bound with {@code property}.
* @param property the property being bound with {@code combo box}.
* @see #unbindEnum(JFXComboBox)
* @deprecated Use {@link ExtendedProperties#selectedItemPropertyFor(ComboBox)}
*/
@SuppressWarnings("unchecked")
@Deprecated
public static void bindEnum(JFXComboBox<?> comboBox, Property<? extends Enum<?>> property) {
unbindEnum(comboBox);
ChangeListener<Number> listener = (a, b, newValue) ->
((Property) property).setValue(property.getValue().getClass().getEnumConstants()[newValue.intValue()]);
comboBox.getSelectionModel().select(property.getValue().ordinal());
comboBox.getProperties().put("FXUtils.bindEnum.listener", listener);
comboBox.getSelectionModel().selectedIndexProperty().addListener(listener);
}
/**
* Unbind combo box selection with given enum property bidirectionally.
* You should <b>only and always</b> use {@code bindEnum} as well as {@code unbindEnum} at the same time.
* @param comboBox the combo box being bound with the property which can be inferred by {@code bindEnum}.
* @see #bindEnum(JFXComboBox, Property)
*/
@SuppressWarnings("unchecked")
@Deprecated
public static void unbindEnum(JFXComboBox<?> comboBox) {
ChangeListener listener = tryCast(comboBox.getProperties().get("FXUtils.bindEnum.listener"), ChangeListener.class).orElse(null);
if (listener == null) return;
comboBox.getSelectionModel().selectedIndexProperty().removeListener(listener);
}
public static void smoothScrolling(ListView<?> listView) {
listView.skinProperty().addListener(o -> {
ScrollBar bar = (ScrollBar) listView.lookup(".scroll-bar");
Node virtualFlow = listView.lookup(".virtual-flow");
double[] frictions = new double[]{0.99, 0.1, 0.05, 0.04, 0.03, 0.02, 0.01, 0.04, 0.01, 0.008, 0.008, 0.008, 0.008, 0.0006, 0.0005, 0.00003, 0.00001};
double[] pushes = new double[]{1};
double[] derivatives = new double[frictions.length];
Timeline timeline = new Timeline();
bar.addEventHandler(MouseEvent.DRAG_DETECTED, e -> timeline.stop());
EventHandler<ScrollEvent> scrollEventHandler = event -> {
if (event.getEventType() == ScrollEvent.SCROLL) {
int direction = event.getDeltaY() > 0 ? -1 : 1;
for (int i = 0; i < pushes.length; ++i)
derivatives[i] += direction * pushes[i];
if (timeline.getStatus() == Animation.Status.STOPPED)
timeline.play();
event.consume();
}
};
bar.addEventHandler(ScrollEvent.ANY, scrollEventHandler);
virtualFlow.setOnScroll(scrollEventHandler);
timeline.getKeyFrames().add(new KeyFrame(Duration.millis(3), event -> {
for (int i = 0; i < derivatives.length; ++i)
derivatives[i] *= frictions[i];
for (int i = 1; i < derivatives.length; ++i)
derivatives[i] += derivatives[i - 1];
double dy = derivatives[derivatives.length - 1];
double height = listView.getLayoutBounds().getHeight();
bar.setValue(Math.min(Math.max(bar.getValue() + dy / height, 0), 1));
if (Math.abs(dy) < 0.001)
timeline.stop();
listView.requestLayout();
}));
timeline.setCycleCount(Animation.INDEFINITE);
});
}
public static <T> T runInUIThread(Supplier<T> supplier) {
if (javafx.application.Platform.isFxApplicationThread()) {
return supplier.get();
} else {
CountDownLatch doneLatch = new CountDownLatch(1);
AtomicReference<T> reference = new AtomicReference<>();
Platform.runLater(() -> {
try {
reference.set(supplier.get());
} finally {
doneLatch.countDown();
}
});
try {
doneLatch.await();
} catch (InterruptedException var3) {
Thread.currentThread().interrupt();
}
return reference.get();
}
}
public static void applyDragListener(Node node, FileFilter filter, Consumer<List<File>> callback) {
applyDragListener(node, filter, callback, null);
}
public static void applyDragListener(Node node, FileFilter filter, Consumer<List<File>> callback, Runnable dragDropped) {
node.setOnDragOver(event -> {
if (event.getGestureSource() != node && event.getDragboard().hasFiles()) {
if (event.getDragboard().getFiles().stream().anyMatch(filter::accept))
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
event.consume();
});
node.setOnDragDropped(event -> {
List<File> files = event.getDragboard().getFiles();
if (files != null) {
List<File> acceptFiles = files.stream().filter(filter::accept).collect(Collectors.toList());
if (!acceptFiles.isEmpty()) {
callback.accept(acceptFiles);
event.setDropCompleted(true);
}
}
if (dragDropped != null)
dragDropped.run();
event.consume();
});
}
public static <T> StringConverter<T> stringConverter(Function<T, String> func) {
return new StringConverter<T>() {
@Override
public String toString(T object) {
return object == null ? "" : func.apply(object);
}
@Override
public T fromString(String string) {
throw new UnsupportedOperationException();
}
};
}
public static <T> Callback<ListView<T>, ListCell<T>> jfxListCellFactory(Function<T, Node> graphicBuilder) {
return view -> new JFXListCell<T>() {
@Override
public void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (!empty) {
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setGraphic(graphicBuilder.apply(item));
}
}
};
}
public static final Interpolator SINE = new Interpolator() {
@Override
protected double curve(double t) {
return Math.sin(t * Math.PI / 2);
}
@Override
public String toString() {
return "Interpolator.SINE";
}
};
public static Runnable withJFXPopupClosing(Runnable runnable, JFXPopup popup) {
return () -> {
runnable.run();
popup.hide();
};
}
}
@@ -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,55 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.effects.JFXDepthManager;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import java.util.function.Consumer;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
* @author huangyuhui
*/
public class InstallerItem extends BorderPane {
private final Consumer<InstallerItem> deleteCallback;
@FXML
private Label lblInstallerArtifact;
@FXML
private Label lblInstallerVersion;
public InstallerItem(String artifact, String version, Consumer<InstallerItem> deleteCallback) {
this.deleteCallback = deleteCallback;
FXUtils.loadFXML(this, "/assets/fxml/version/installer-item.fxml");
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
lblInstallerArtifact.setText(artifact);
lblInstallerVersion.setText(i18n("archive.version") + ": " + version);
}
@FXML
private void onDelete() {
deleteCallback.accept(this);
}
}
@@ -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,601 +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="0" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="lblDownloadSource" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblCommonPath" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblLang" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblProxy" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace min="-2" pref="28" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<Component id="cboProxyType" pref="156" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblProxyHost" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="txtProxyHost" min="-2" pref="80" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblProxyPort" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="txtProxyPort" min="-2" pref="80" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblProxyUserName" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="txtProxyUsername" min="-2" pref="80" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblProxyPassword" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="txtProxyPassword" min="-2" pref="80" max="-2" attributes="0"/>
</Group>
<Component id="cboLang" alignment="1" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Component id="txtCommonPath" max="32767" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="btnSetCommonPath" min="-2" max="-2" attributes="0"/>
</Group>
<Component id="cboDownloadSource" alignment="1" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lblCommonPath" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="txtCommonPath" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="btnSetCommonPath" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lblDownloadSource" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cboDownloadSource" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="cboLang" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="lblLang" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="txtProxyHost" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="lblProxy" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="lblProxyHost" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="txtProxyPort" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="lblProxyPort" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="txtProxyUsername" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="lblProxyUserName" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="txtProxyPassword" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="lblProxyPassword" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="cboProxyType" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="69" max="32767" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="btnCheckUpdate" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="btnMCBBS" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="lblProxy">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.proxy" replaceFormat="C.i18n(&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>
<Component class="javax.swing.JComboBox" name="cboProxyType">
<Properties>
<Property name="model" type="javax.swing.ComboBoxModel" editor="org.netbeans.modules.form.editors2.ComboBoxModelEditor">
<StringArray count="3">
<StringItem index="0" value="Direct"/>
<StringItem index="1" value="HTTP"/>
<StringItem index="2" value="Socks"/>
</StringArray>
</Property>
</Properties>
<Events>
<EventHandler event="itemStateChanged" listener="java.awt.event.ItemListener" parameters="java.awt.event.ItemEvent" handler="cboProxyTypeItemStateChanged"/>
</Events>
<AuxValues>
<AuxValue name="JavaCodeGenerator_TypeParameters" type="java.lang.String" value="&lt;String&gt;"/>
</AuxValues>
</Component>
</SubComponents>
</Container>
<Container class="javax.swing.JPanel" name="pnlUI">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation())"/>
</AuxValues>
<Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JTabbedPaneSupportLayout$JTabbedPaneConstraintsDescription">
<JTabbedPaneConstraints tabName="UI">
<Property name="tabTitle" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.tab.ui" replaceFormat="C.i18n(&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="163" max="32767" attributes="0"/>
</Group>
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="lblBackground" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblFont" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="lblTheme" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="cboTheme" max="32767" attributes="0"/>
<Group type="102" alignment="0" attributes="0">
<Group type="103" groupAlignment="0" attributes="0">
<Component id="txtBackgroundPath" max="32767" attributes="0"/>
<Component id="cboFontFamily" max="32767" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" max="-2" attributes="0">
<Component id="btnSelBackgroundPath" max="32767" attributes="0"/>
<Component id="spinnerFontSize" max="32767" attributes="0"/>
</Group>
</Group>
<Group type="102" alignment="0" attributes="0">
<Component id="lblExample" min="-2" max="-2" attributes="0"/>
<EmptySpace min="0" pref="0" max="32767" attributes="0"/>
</Group>
</Group>
</Group>
</Group>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="lblBackground" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="txtBackgroundPath" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="btnSelBackgroundPath" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="cboFontFamily" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="lblFont" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="spinnerFontSize" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="lblExample" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="cboTheme" alignment="3" min="-2" pref="26" max="-2" attributes="0"/>
<Component id="lblTheme" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="63" max="32767" attributes="0"/>
<Group type="103" groupAlignment="3" attributes="0">
<Component id="chkEnableShadow" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="chkEnableBlur" alignment="3" min="-2" max="-2" attributes="0"/>
<Component id="chkEnableAnimation" alignment="3" min="-2" max="-2" attributes="0"/>
</Group>
<EmptySpace max="-2" attributes="0"/>
<Component id="chkDecorated" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="lblTheme">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.theme" replaceFormat="C.i18n(&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="252" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="lblAbout" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="lblAbout">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/jackhuang/hmcl/lang/I18N.properties" key="launcher.about" replaceFormat="C.i18n(&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,672 +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 java.net.Proxy;
import javax.swing.DefaultComboBoxModel;
import javax.swing.filechooser.FileNameExtensionFilter;
import org.jackhuang.hmcl.util.C;
import org.jackhuang.hmcl.api.HMCLog;
import org.jackhuang.hmcl.api.ui.Theme;
import org.jackhuang.hmcl.setting.Settings;
import org.jackhuang.hmcl.core.download.DownloadType;
import org.jackhuang.hmcl.util.MathUtils;
import org.jackhuang.hmcl.util.sys.IOUtils;
import org.jackhuang.hmcl.util.ui.MessageBox;
import org.jackhuang.hmcl.util.lang.SupportedLocales;
import org.jackhuang.hmcl.util.ui.JFontComboBox;
import org.jackhuang.hmcl.util.ui.JSystemFileChooser;
import org.jackhuang.hmcl.util.ui.Page;
import org.jackhuang.hmcl.util.ui.SwingUtils;
/**
*
* @author huangyuhui
*/
public class LauncherSettingsPanel extends RepaintPage {
public LauncherSettingsPanel() {
setRepainter(this);
}
void initGui() {
initComponents();
animationEnabled = Settings.getInstance().isEnableAnimation();
DefaultComboBoxModel<String> d = new DefaultComboBoxModel<>();
for (DownloadType type : DownloadType.values())
d.addElement(type.getName());
cboDownloadSource.setModel(d);
d = new DefaultComboBoxModel<>();
int id = 0;
for (SupportedLocales type : SupportedLocales.values()) {
d.addElement(type.showString());
if (type.name().equals(Settings.getInstance().getLocalization()))
id = type.ordinal();
}
cboLang.setModel(d);
cboLang.setSelectedIndex(id);
DefaultComboBoxModel<Theme> g = new DefaultComboBoxModel<>();
for (Theme t : Theme.THEMES.values())
g.addElement(t);
cboTheme.setModel(g);
txtBackgroundPath.setText(Settings.getInstance().getBgpath());
txtCommonPath.setText(Settings.getInstance().getCommonpath());
txtProxyHost.setText(Settings.getInstance().getProxyHost());
txtProxyPort.setText(Settings.getInstance().getProxyPort());
txtProxyUsername.setText(Settings.getInstance().getProxyUserName());
txtProxyPassword.setText(Settings.getInstance().getProxyPassword());
spinnerFontSize.setValue(Settings.getInstance().getFontSize());
cboFontFamily.setSelectedItem(Settings.getInstance().getFontFamily());
cboDownloadSource.setSelectedIndex(Settings.getInstance().getDownloadType());
cboProxyType.setSelectedIndex(Settings.getInstance().getProxyType().ordinal());
cboTheme.setSelectedItem(Settings.getInstance().getTheme());
chkEnableShadow.setSelected(Settings.getInstance().isEnableShadow());
chkEnableBlur.setSelected(Settings.getInstance().isEnableBlur());
chkEnableAnimation.setSelected(Settings.getInstance().isEnableAnimation());
chkDecorated.setSelected(Settings.getInstance().isDecorated());
}
@Override
public void onCreate() {
initGui();
super.onCreate();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
lblModpack = new javax.swing.JLabel();
lblRestart = new javax.swing.JLabel();
tabLauncherSettings = new NewTabPane();
pnlGeneral = new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation());
lblProxy = new javax.swing.JLabel();
lblProxyHost = new javax.swing.JLabel();
txtProxyHost = new javax.swing.JTextField();
lblProxyPort = new javax.swing.JLabel();
txtProxyPort = new javax.swing.JTextField();
lblProxyUserName = new javax.swing.JLabel();
txtProxyUsername = new javax.swing.JTextField();
lblProxyPassword = new javax.swing.JLabel();
txtProxyPassword = new javax.swing.JTextField();
cboDownloadSource = new javax.swing.JComboBox();
lblDownloadSource = new javax.swing.JLabel();
lblCommonPath = new javax.swing.JLabel();
txtCommonPath = new javax.swing.JTextField();
btnSetCommonPath = new javax.swing.JButton();
btnCheckUpdate = new javax.swing.JButton();
btnMCBBS = new javax.swing.JButton();
cboLang = new javax.swing.JComboBox();
lblLang = new javax.swing.JLabel();
cboProxyType = new javax.swing.JComboBox<>();
pnlUI = new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation());
lblTheme = new javax.swing.JLabel();
cboTheme = new javax.swing.JComboBox();
lblBackground = new javax.swing.JLabel();
txtBackgroundPath = new javax.swing.JTextField();
chkEnableShadow = new javax.swing.JCheckBox();
chkEnableBlur = new javax.swing.JCheckBox();
chkEnableAnimation = new javax.swing.JCheckBox();
chkDecorated = new javax.swing.JCheckBox();
btnSelBackgroundPath = new javax.swing.JButton();
cboFontFamily = new JFontComboBox();
lblFont = new javax.swing.JLabel();
spinnerFontSize = new javax.swing.JSpinner();
lblExample = new javax.swing.JLabel();
pnlAbout = new Page().setAnimationEnabled(Settings.getInstance().isEnableAnimation());
lblAbout = new javax.swing.JLabel();
lblModpack.setText(C.i18n("launcher.modpack")); // NOI18N
lblModpack.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR));
lblModpack.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
lblModpackMouseClicked(evt);
}
});
lblRestart.setText(C.i18n("launcher.restart")); // NOI18N
lblProxy.setText(C.i18n("launcher.proxy")); // NOI18N
lblProxyHost.setText(C.i18n("proxy.host")); // NOI18N
txtProxyHost.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
txtProxyHostFocusLost(evt);
}
});
lblProxyPort.setText(C.i18n("proxy.port")); // NOI18N
txtProxyPort.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
txtProxyPortFocusLost(evt);
}
});
lblProxyUserName.setText(C.i18n("proxy.username")); // NOI18N
txtProxyUsername.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
txtProxyUsernameFocusLost(evt);
}
});
lblProxyPassword.setText(C.i18n("proxy.password")); // NOI18N
txtProxyPassword.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
txtProxyPasswordFocusLost(evt);
}
});
cboDownloadSource.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
cboDownloadSourceItemStateChanged(evt);
}
});
lblDownloadSource.setText(C.i18n("launcher.download_source")); // NOI18N
lblCommonPath.setText(C.i18n("launcher.common_location")); // NOI18N
txtCommonPath.setToolTipText(C.i18n("launcher.commpath_tooltip")); // NOI18N
txtCommonPath.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
txtCommonPathFocusLost(evt);
}
});
btnSetCommonPath.setText(C.i18n("ui.button.explore")); // NOI18N
btnSetCommonPath.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnSetCommonPathActionPerformed(evt);
}
});
btnCheckUpdate.setText(C.i18n("launcher.update_launcher")); // NOI18N
btnCheckUpdate.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnCheckUpdateActionPerformed(evt);
}
});
btnMCBBS.setText("MCBBS");
btnMCBBS.setToolTipText("");
btnMCBBS.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnMCBBSActionPerformed(evt);
}
});
cboLang.setModel(new DefaultComboBoxModel(new String[]{C.i18n("color.blue"),C.i18n("color.green"),C.i18n("color.purple"),C.i18n("color.dark_blue"),C.i18n("color.orange"),C.i18n("color.red")}));
cboLang.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
cboLangItemStateChanged(evt);
}
});
lblLang.setText(C.i18n("launcher.lang")); // NOI18N
cboProxyType.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Direct", "HTTP", "Socks" }));
cboProxyType.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
cboProxyTypeItemStateChanged(evt);
}
});
javax.swing.GroupLayout pnlGeneralLayout = new javax.swing.GroupLayout(pnlGeneral);
pnlGeneral.setLayout(pnlGeneralLayout);
pnlGeneralLayout.setHorizontalGroup(
pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlGeneralLayout.createSequentialGroup()
.addContainerGap()
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlGeneralLayout.createSequentialGroup()
.addComponent(btnCheckUpdate)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(btnMCBBS)
.addGap(0, 0, Short.MAX_VALUE))
.addGroup(pnlGeneralLayout.createSequentialGroup()
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblDownloadSource)
.addComponent(lblCommonPath)
.addComponent(lblLang)
.addComponent(lblProxy))
.addGap(28, 28, 28)
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlGeneralLayout.createSequentialGroup()
.addComponent(cboProxyType, 0, 156, Short.MAX_VALUE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblProxyHost)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(txtProxyHost, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblProxyPort)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(txtProxyPort, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblProxyUserName)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(txtProxyUsername, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblProxyPassword)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(txtProxyPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 80, javax.swing.GroupLayout.PREFERRED_SIZE))
.addComponent(cboLang, javax.swing.GroupLayout.Alignment.TRAILING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(pnlGeneralLayout.createSequentialGroup()
.addComponent(txtCommonPath)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(btnSetCommonPath))
.addComponent(cboDownloadSource, javax.swing.GroupLayout.Alignment.TRAILING, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
.addContainerGap())
);
pnlGeneralLayout.setVerticalGroup(
pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlGeneralLayout.createSequentialGroup()
.addContainerGap()
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblCommonPath)
.addComponent(txtCommonPath, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(btnSetCommonPath, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblDownloadSource)
.addComponent(cboDownloadSource, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(cboLang, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblLang))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(txtProxyHost, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblProxy)
.addComponent(lblProxyHost)
.addComponent(txtProxyPort, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblProxyPort)
.addComponent(txtProxyUsername, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblProxyUserName)
.addComponent(txtProxyPassword, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblProxyPassword)
.addComponent(cboProxyType, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 69, Short.MAX_VALUE)
.addGroup(pnlGeneralLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(btnCheckUpdate, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(btnMCBBS, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap())
);
tabLauncherSettings.addTab(C.i18n("launcher.tab.general"), pnlGeneral); // NOI18N
lblTheme.setText(C.i18n("launcher.theme")); // NOI18N
cboTheme.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
cboThemeItemStateChanged(evt);
}
});
lblBackground.setText(C.i18n("launcher.background_location")); // NOI18N
lblBackground.setToolTipText("");
txtBackgroundPath.setToolTipText(C.i18n("launcher.background_tooltip")); // NOI18N
txtBackgroundPath.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(java.awt.event.FocusEvent evt) {
txtBackgroundPathFocusLost(evt);
}
});
chkEnableShadow.setText(C.i18n("launcher.enable_shadow")); // NOI18N
chkEnableShadow.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
chkEnableShadowItemStateChanged(evt);
}
});
chkEnableBlur.setText(C.i18n("launcher.enable_blur")); // NOI18N
chkEnableBlur.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
chkEnableBlurItemStateChanged(evt);
}
});
chkEnableAnimation.setText(C.i18n("launcher.enable_animation")); // NOI18N
chkEnableAnimation.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
chkEnableAnimationItemStateChanged(evt);
}
});
chkDecorated.setText(C.i18n("launcher.decorated")); // NOI18N
chkDecorated.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
chkDecoratedItemStateChanged(evt);
}
});
btnSelBackgroundPath.setText(C.i18n("ui.button.explore")); // NOI18N
btnSelBackgroundPath.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnSelBackgroundPathActionPerformed(evt);
}
});
cboFontFamily.addItemListener(new java.awt.event.ItemListener() {
public void itemStateChanged(java.awt.event.ItemEvent evt) {
cboFontFamilyItemStateChanged(evt);
}
});
lblFont.setText(C.i18n("launcher.log_font")); // NOI18N
spinnerFontSize.addChangeListener(new javax.swing.event.ChangeListener() {
public void stateChanged(javax.swing.event.ChangeEvent evt) {
spinnerFontSizeStateChanged(evt);
}
});
lblExample.setText("19:10:10 [Client Thread/INFO]: Example");
javax.swing.GroupLayout pnlUILayout = new javax.swing.GroupLayout(pnlUI);
pnlUI.setLayout(pnlUILayout);
pnlUILayout.setHorizontalGroup(
pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlUILayout.createSequentialGroup()
.addContainerGap()
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlUILayout.createSequentialGroup()
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(chkDecorated)
.addGroup(pnlUILayout.createSequentialGroup()
.addComponent(chkEnableShadow)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(chkEnableBlur)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(chkEnableAnimation)))
.addGap(0, 163, Short.MAX_VALUE))
.addGroup(pnlUILayout.createSequentialGroup()
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblBackground)
.addComponent(lblFont)
.addComponent(lblTheme))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(cboTheme, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addGroup(pnlUILayout.createSequentialGroup()
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(txtBackgroundPath)
.addComponent(cboFontFamily, 0, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addComponent(btnSelBackgroundPath, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(spinnerFontSize)))
.addGroup(pnlUILayout.createSequentialGroup()
.addComponent(lblExample)
.addGap(0, 0, Short.MAX_VALUE)))))
.addContainerGap())
);
pnlUILayout.setVerticalGroup(
pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlUILayout.createSequentialGroup()
.addContainerGap()
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(lblBackground)
.addComponent(txtBackgroundPath, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(btnSelBackgroundPath, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(cboFontFamily, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblFont)
.addComponent(spinnerFontSize, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblExample)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(cboTheme, javax.swing.GroupLayout.PREFERRED_SIZE, 26, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblTheme))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 63, Short.MAX_VALUE)
.addGroup(pnlUILayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(chkEnableShadow)
.addComponent(chkEnableBlur)
.addComponent(chkEnableAnimation))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(chkDecorated)
.addContainerGap())
);
tabLauncherSettings.addTab(C.i18n("launcher.tab.ui"), pnlUI); // NOI18N
lblAbout.setText(C.i18n("launcher.about")); // NOI18N
lblAbout.setCursor(new java.awt.Cursor(java.awt.Cursor.DEFAULT_CURSOR));
javax.swing.GroupLayout pnlAboutLayout = new javax.swing.GroupLayout(pnlAbout);
pnlAbout.setLayout(pnlAboutLayout);
pnlAboutLayout.setHorizontalGroup(
pnlAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlAboutLayout.createSequentialGroup()
.addContainerGap()
.addComponent(lblAbout, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(252, Short.MAX_VALUE))
);
pnlAboutLayout.setVerticalGroup(
pnlAboutLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(pnlAboutLayout.createSequentialGroup()
.addContainerGap()
.addComponent(lblAbout, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
tabLauncherSettings.addTab(C.i18n("launcher.tab.about"), pnlAbout); // NOI18N
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addGap(10, 10, 10)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(lblModpack, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(lblRestart))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
.addComponent(tabLauncherSettings)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(tabLauncherSettings)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblModpack, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(lblRestart)
.addContainerGap())
);
tabLauncherSettings.getAccessibleContext().setAccessibleName("");
}// </editor-fold>//GEN-END:initComponents
private void cboDownloadSourceItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboDownloadSourceItemStateChanged
Settings.getInstance().setDownloadType(cboDownloadSource.getSelectedIndex());
}//GEN-LAST:event_cboDownloadSourceItemStateChanged
private void btnSelBackgroundPathActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSelBackgroundPathActionPerformed
JSystemFileChooser fc = new JSystemFileChooser();
fc.setFileSelectionMode(JSystemFileChooser.FILES_ONLY);
fc.setDialogTitle(C.i18n("launcher.choose_bgpath"));
fc.setMultiSelectionEnabled(false);
fc.setFileFilter(new FileNameExtensionFilter("*.png", "png"));
fc.addChoosableFileFilter(new FileNameExtensionFilter("*.jpg", "jpg"));
if (fc.showOpenDialog(this) != JSystemFileChooser.APPROVE_OPTION || fc.getSelectedFile() == null)
return;
try {
String path = fc.getSelectedFile().getCanonicalPath();
path = IOUtils.removeLastSeparator(path);
txtBackgroundPath.setText(path);
Settings.getInstance().setBgpath(path);
MainFrame.INSTANCE.loadBackground();
} catch (IOException e) {
HMCLog.warn("Failed to set background path.", e);
MessageBox.show(C.i18n("ui.label.failed_set") + e.getMessage());
}
}//GEN-LAST:event_btnSelBackgroundPathActionPerformed
private void txtBackgroundPathFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtBackgroundPathFocusLost
Settings.getInstance().setBgpath(txtBackgroundPath.getText());
MainFrame.INSTANCE.loadBackground();
}//GEN-LAST:event_txtBackgroundPathFocusLost
private void btnCheckUpdateActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnCheckUpdateActionPerformed
Settings.UPDATE_CHECKER.process(true);
Settings.UPDATE_CHECKER.checkOutdate();
}//GEN-LAST:event_btnCheckUpdateActionPerformed
private void cboThemeItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboThemeItemStateChanged
Settings.getInstance().setTheme(((Theme) cboTheme.getSelectedItem()).id);
}//GEN-LAST:event_cboThemeItemStateChanged
private void lblModpackMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_lblModpackMouseClicked
SwingUtils.openLink("http://huangyuhui.duapp.com/link.php?type=modpack");
}//GEN-LAST:event_lblModpackMouseClicked
private void cboLangItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboLangItemStateChanged
Settings.getInstance().setLocalization(SupportedLocales.values()[cboLang.getSelectedIndex()].name());
}//GEN-LAST:event_cboLangItemStateChanged
private void chkEnableShadowItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkEnableShadowItemStateChanged
Settings.getInstance().setEnableShadow(chkEnableShadow.isSelected());
}//GEN-LAST:event_chkEnableShadowItemStateChanged
private void chkDecoratedItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkDecoratedItemStateChanged
Settings.getInstance().setDecorated(chkDecorated.isSelected());
}//GEN-LAST:event_chkDecoratedItemStateChanged
private void txtProxyHostFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtProxyHostFocusLost
Settings.getInstance().setProxyHost(txtProxyHost.getText());
}//GEN-LAST:event_txtProxyHostFocusLost
private void txtProxyPortFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtProxyPortFocusLost
Settings.getInstance().setProxyPort(txtProxyPort.getText());
}//GEN-LAST:event_txtProxyPortFocusLost
private void txtProxyUsernameFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtProxyUsernameFocusLost
Settings.getInstance().setProxyUserName(txtProxyUsername.getText());
}//GEN-LAST:event_txtProxyUsernameFocusLost
private void txtProxyPasswordFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtProxyPasswordFocusLost
Settings.getInstance().setProxyPassword(txtProxyPassword.getText());
}//GEN-LAST:event_txtProxyPasswordFocusLost
private void btnMCBBSActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnMCBBSActionPerformed
SwingUtils.openLink(C.URL_PUBLISH);
}//GEN-LAST:event_btnMCBBSActionPerformed
private void chkEnableBlurItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkEnableBlurItemStateChanged
Settings.getInstance().setEnableBlur(chkEnableBlur.isSelected());
}//GEN-LAST:event_chkEnableBlurItemStateChanged
private void chkEnableAnimationItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_chkEnableAnimationItemStateChanged
Settings.getInstance().setEnableAnimation(chkEnableAnimation.isSelected());
}//GEN-LAST:event_chkEnableAnimationItemStateChanged
private void btnSetCommonPathActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnSetCommonPathActionPerformed
JSystemFileChooser fc = new JSystemFileChooser();
fc.setFileSelectionMode(JSystemFileChooser.DIRECTORIES_ONLY);
fc.setDialogTitle(C.i18n("launcher.choose_commonpath"));
fc.setMultiSelectionEnabled(false);
if (fc.showOpenDialog(this) != JSystemFileChooser.APPROVE_OPTION || fc.getSelectedFile() == null)
return;
try {
String path = fc.getSelectedFile().getCanonicalPath();
txtCommonPath.setText(path);
Settings.getInstance().setCommonpath(path);
} catch (IOException e) {
HMCLog.warn("Failed to set common path.", e);
MessageBox.show(C.i18n("ui.label.failed_set") + e.getMessage());
}
}//GEN-LAST:event_btnSetCommonPathActionPerformed
private void txtCommonPathFocusLost(java.awt.event.FocusEvent evt) {//GEN-FIRST:event_txtCommonPathFocusLost
Settings.getInstance().setCommonpath(txtCommonPath.getText());
}//GEN-LAST:event_txtCommonPathFocusLost
private void cboFontFamilyItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboFontFamilyItemStateChanged
Settings.getInstance().setFontFamily(evt.getItem().toString());
lblExample.setFont(Settings.getInstance().getConsoleFont());
}//GEN-LAST:event_cboFontFamilyItemStateChanged
private void spinnerFontSizeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_spinnerFontSizeStateChanged
Settings.getInstance().setFontSize(MathUtils.parseInt(spinnerFontSize.getValue().toString(), 12));
lblExample.setFont(Settings.getInstance().getConsoleFont());
}//GEN-LAST:event_spinnerFontSizeStateChanged
private void cboProxyTypeItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboProxyTypeItemStateChanged
Settings.getInstance().setProxyType(Proxy.Type.values()[cboProxyType.getSelectedIndex()]);
}//GEN-LAST:event_cboProxyTypeItemStateChanged
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton btnCheckUpdate;
private javax.swing.JButton btnMCBBS;
private javax.swing.JButton btnSelBackgroundPath;
private javax.swing.JButton btnSetCommonPath;
private javax.swing.JComboBox cboDownloadSource;
private javax.swing.JComboBox cboFontFamily;
private javax.swing.JComboBox cboLang;
private javax.swing.JComboBox<String> cboProxyType;
private javax.swing.JComboBox cboTheme;
private javax.swing.JCheckBox chkDecorated;
private javax.swing.JCheckBox chkEnableAnimation;
private javax.swing.JCheckBox chkEnableBlur;
private javax.swing.JCheckBox chkEnableShadow;
private javax.swing.JLabel lblAbout;
private javax.swing.JLabel lblBackground;
private javax.swing.JLabel lblCommonPath;
private javax.swing.JLabel lblDownloadSource;
private javax.swing.JLabel lblExample;
private javax.swing.JLabel lblFont;
private javax.swing.JLabel lblLang;
private javax.swing.JLabel lblModpack;
private javax.swing.JLabel lblProxy;
private javax.swing.JLabel lblProxyHost;
private javax.swing.JLabel lblProxyPassword;
private javax.swing.JLabel lblProxyPort;
private javax.swing.JLabel lblProxyUserName;
private javax.swing.JLabel lblRestart;
private javax.swing.JLabel lblTheme;
private javax.swing.JPanel pnlAbout;
private javax.swing.JPanel pnlGeneral;
private javax.swing.JPanel pnlUI;
private javax.swing.JSpinner spinnerFontSize;
private javax.swing.JTabbedPane tabLauncherSettings;
private javax.swing.JTextField txtBackgroundPath;
private javax.swing.JTextField txtCommonPath;
private javax.swing.JTextField txtProxyHost;
private javax.swing.JTextField txtProxyPassword;
private javax.swing.JTextField txtProxyPort;
private javax.swing.JTextField txtProxyUsername;
// End of variables declaration//GEN-END:variables
}
@@ -1,254 +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;
Log4jHandler logHandler;
public LaunchingUIDaemon() {
HMCLApi.EVENT_BUS.channel(LaunchingStateChangedEvent.class).register(LAUNCHING_STATE_CHANGED);
HMCLApi.EVENT_BUS.channel(LaunchEvent.class).register(p -> {
GameLauncher obj = (GameLauncher) p.getSource();
HMCLGameLauncher.GameLauncherTag tag = (HMCLGameLauncher.GameLauncherTag) obj.getTag();
if (tag.launcherVisibility == LauncherVisibility.CLOSE && !LogWindow.INSTANCE.isVisible()) {
HMCLog.log("Without the option of keeping the launcher visible, this application will exit and will NOT catch game logs, but you can turn on \"Debug Mode\".");
System.exit(0);
} else if (tag.launcherVisibility == LauncherVisibility.KEEP)
MainFrame.INSTANCE.closeMessage();
else {
if (LogWindow.INSTANCE.isVisible())
LogWindow.INSTANCE.setExit(() -> true);
HMCLApi.EVENT_BUS.fireChannel(new LaunchingStateChangedEvent(obj, LaunchingState.WaitingForGameLaunching));
}
// We promise that JavaProcessMonitor.tag is LauncherVisibility
// See events below.
ProcessMonitor monitor = new ProcessMonitor(p.getValue());
PipedOutputStream os = new PipedOutputStream();
monitor.setTag(obj);
try {
logHandler = new Log4jHandler(monitor, os, false);
logHandler.addForbiddenToken(obj.getLoginResult().getAccessToken(), "<access token>");
logHandler.addForbiddenToken(obj.getLoginResult().getSession(), "<session>");
logHandler.addForbiddenToken(obj.getLoginResult().getUserId(), "<uuid>");
logHandler.addForbiddenToken("Setting user: " + obj.getLoginResult().getUserName(), "<player>");
logHandler.start();
} catch(Exception e) {
HMCLog.err("", e);
}
monitor.registerPrintlnEvent(new PrintlnProcessor(obj, os));
monitor.start();
});
HMCLApi.EVENT_BUS.channel(LaunchSucceededEvent.class).register(p -> {
int state = ((HMCLGameLauncher.GameLauncherTag) ((GameLauncher) p.getSource()).getTag()).state;
if (state == 1)
LAUNCH_FINISHER.accept(p);
else if (state == 2)
LAUNCH_SCRIPT_FINISHER.accept(p);
});
HMCLApi.EVENT_BUS.channel(JavaProcessStoppedEvent.class).register(event -> {
GameLauncher launcher = ((GameLauncher) ((ProcessMonitor) event.getSource()).getTag());
HMCLApi.EVENT_BUS.fireChannel(new LaunchingStateChangedEvent(launcher, LaunchingState.Done));
checkExit(unpackProcessMonitor(event.getSource()));
});
HMCLApi.EVENT_BUS.channel(JavaProcessExitedAbnormallyEvent.class).register(event -> {
ProcessMonitor monitor = (ProcessMonitor) event.getSource();
int exitCode = event.getValue().getExitCode();
HMCLog.err("The game exited abnormally, exit code: " + exitCode);
monitor.waitForCommandLineCompletion();
String[] logs = event.getValue().getStdOutLines().toArray(new String[0]);
String errorText = null;
for (String s : logs) {
int pos = s.lastIndexOf("#@!@#");
if (pos >= 0 && pos < s.length() - "#@!@#".length() - 1) {
errorText = s.substring(pos + "#@!@#".length()).trim();
break;
}
}
String msg = C.i18n("launch.exited_abnormally") + " exit code: " + exitCode;
if (errorText != null)
msg += ", advice: " + MinecraftCrashAdvicer.getAdvice(FileUtils.readQuietly(new File(errorText)));
HMCLog.err(msg);
SwingUtilities.invokeLater(() -> LogWindow.INSTANCE.setVisible(true));
noExitThisTime = true;
});
HMCLApi.EVENT_BUS.channel(JVMLaunchFailedEvent.class).register(event -> {
int exitCode = event.getValue().getExitCode();
HMCLog.err("Cannot create jvm, exit code: " + exitCode);
SwingUtilities.invokeLater(() -> LogWindow.INSTANCE.setVisible(true));
noExitThisTime = true;
});
}
static LauncherVisibility unpackProcessMonitor(Object obj) {
GameLauncher launcher = ((GameLauncher) ((ProcessMonitor) obj).getTag());
HMCLGameLauncher.GameLauncherTag tag = (HMCLGameLauncher.GameLauncherTag) launcher.getTag();
return tag.launcherVisibility;
}
boolean noExitThisTime = false;
void runGame(Profile profile) {
MainFrame.INSTANCE.showMessage(C.i18n("ui.message.launching"));
profile.launcher().genLaunchCode(value -> {
DefaultPlugin.INSTANCE.saveAuthenticatorConfig();
((HMCLGameLauncher.GameLauncherTag) value.getTag()).state = 1;
}, MainFrame.INSTANCE::failed, Settings.getInstance().getAuthenticator().getPassword());
}
void testGame(Profile profile) {
MainFrame.INSTANCE.showMessage(C.i18n("ui.message.launching"));
profile.launcher().genLaunchCode(value -> {
DefaultPlugin.INSTANCE.saveAuthenticatorConfig();
((HMCLGameLauncher.GameLauncherTag) value.getTag()).state = 1;
((HMCLGameLauncher.GameLauncherTag) value.getTag()).launcherVisibility = LauncherVisibility.KEEP;
}, MainFrame.INSTANCE::failed, Settings.getInstance().getAuthenticator().getPassword());
}
void makeLaunchScript(Profile profile) {
MainFrame.INSTANCE.showMessage(C.i18n("ui.message.launching"));
profile.launcher().genLaunchCode(value -> {
DefaultPlugin.INSTANCE.saveAuthenticatorConfig();
((HMCLGameLauncher.GameLauncherTag) value.getTag()).state = 2;
}, MainFrame.INSTANCE::failed, Settings.getInstance().getAuthenticator().getPassword());
}
class PrintlnProcessor implements Consumer<PrintlnEvent> {
GameLauncher launcher;
PipedOutputStream os;
public PrintlnProcessor(GameLauncher launcher, PipedOutputStream os) {
this.launcher = launcher;
this.os = os;
}
@Override
public void accept(PrintlnEvent t) {
String log = t.getLine();
if (!t.isError() && logHandler != null) {
logHandler.newLogLine(log + C.LINE_SEPARATOR);
}
else System.err.println(t.getLine());
HMCLGameLauncher.GameLauncherTag tag = (HMCLGameLauncher.GameLauncherTag) launcher.getTag();
LauncherVisibility l = tag.launcherVisibility;
if (t.getLine().contains("LWJGL Version: ") && l != LauncherVisibility.KEEP)
SwingUtilities.invokeLater(() -> {
if (l != LauncherVisibility.HIDE_AND_REOPEN)
MainFrame.INSTANCE.dispose();
else { // If current state is 'hide and reopen', closes the main window and reset the state to normal.
MainFrame.INSTANCE.setVisible(false);
HMCLApi.EVENT_BUS.fireChannel(new LaunchingStateChangedEvent(launcher, LaunchingState.Done));
}
});
}
}
private static final Consumer<LaunchingStateChangedEvent> LAUNCHING_STATE_CHANGED = t -> {
String message = null;
switch (t.getValue()) {
case LoggingIn:
message = "launch.state.logging_in";
break;
case GeneratingLaunchingCodes:
message = "launch.state.generating_launching_codes";
break;
case DownloadingLibraries:
message = "launch.state.downloading_libraries";
break;
case DecompressingNatives:
message = "launch.state.decompressing_natives";
break;
case WaitingForGameLaunching:
message = "launch.state.waiting_launching";
break;
case Done:
MainFrame.INSTANCE.closeMessage();
return;
}
MainFrame.INSTANCE.showMessage(C.i18n(message));
};
private static final Consumer<LaunchSucceededEvent> LAUNCH_FINISHER = event -> {
try {
((GameLauncher) event.getSource()).launch(event.getValue());
} catch (IOException e) {
MainFrame.INSTANCE.failed(C.i18n("launch.failed_creating_process") + "\n" + e.getMessage());
HMCLog.err("Failed to launch when creating a new process.", e);
}
};
private void checkExit(LauncherVisibility v) {
if (v == LauncherVisibility.HIDE_AND_REOPEN) {
MainFrame.INSTANCE.setVisible(true);
} else if (v != LauncherVisibility.KEEP && !LogWindow.INSTANCE.isVisible() && !noExitThisTime) {
System.exit(0);
} else {
noExitThisTime = false;
}
}
private static final Consumer<LaunchSucceededEvent> LAUNCH_SCRIPT_FINISHER = event -> {
try {
String s = MessageBox.showInputDialog(C.i18n("mainwindow.enter_script_name"));
if (StrUtils.isNotBlank(s))
MessageBox.show(C.i18n("mainwindow.make_launch_succeed") + " " + ((GameLauncher) event.getSource()).makeLauncher(s, event.getValue()).getAbsolutePath());
} catch (IOException ex) {
MessageBox.show(C.i18n("mainwindow.make_launch_script_failed"));
HMCLog.err("Failed to create script file.", ex);
}
MainFrame.INSTANCE.closeMessage();
};
}
@@ -0,0 +1,137 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.utils.JFXUtilities;
import javafx.application.Platform;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.event.EventBus;
import org.jackhuang.hmcl.event.RefreshedVersionsEvent;
import org.jackhuang.hmcl.game.HMCLGameRepository;
import org.jackhuang.hmcl.game.ModpackHelper;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.task.TaskExecutor;
import org.jackhuang.hmcl.ui.account.AccountAdvancedListItem;
import org.jackhuang.hmcl.ui.account.AddAccountPane;
import org.jackhuang.hmcl.ui.construct.AdvancedListBox;
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
import org.jackhuang.hmcl.ui.profile.ProfileAdvancedListItem;
import org.jackhuang.hmcl.ui.versions.GameAdvancedListItem;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.util.io.CompressingUtils;
import java.io.File;
import java.util.concurrent.atomic.AtomicReference;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class LeftPaneController extends AdvancedListBox {
public LeftPaneController() {
AccountAdvancedListItem accountListItem = new AccountAdvancedListItem();
accountListItem.setOnAction(e -> Controllers.navigate(Controllers.getAccountListPage()));
accountListItem.accountProperty().bind(Accounts.selectedAccountProperty());
GameAdvancedListItem gameListItem = new GameAdvancedListItem();
gameListItem.actionButtonVisibleProperty().bind(Profiles.selectedVersionProperty().isNotNull());
gameListItem.setOnAction(e -> {
Profile profile = Profiles.getSelectedProfile();
String version = Profiles.getSelectedVersion();
if (version == null) {
Controllers.navigate(Controllers.getGameListPage());
} else {
Versions.modifyGameSettings(profile, version);
}
});
ProfileAdvancedListItem profileListItem = new ProfileAdvancedListItem();
profileListItem.setOnAction(e -> Controllers.navigate(Controllers.getProfileListPage()));
profileListItem.profileProperty().bind(Profiles.selectedProfileProperty());
AdvancedListItem gameItem = new AdvancedListItem();
gameItem.setImage(new Image("/assets/img/bookshelf.png"));
gameItem.setTitle(i18n("version.manage"));
gameItem.setOnAction(e -> Controllers.navigate(Controllers.getGameListPage()));
AdvancedListItem launcherSettingsItem = new AdvancedListItem();
launcherSettingsItem.setImage(new Image("/assets/img/command.png"));
launcherSettingsItem.setTitle(i18n("settings.launcher"));
launcherSettingsItem.setOnAction(e -> Controllers.navigate(Controllers.getSettingsPage()));
this
.startCategory(i18n("account").toUpperCase())
.add(accountListItem)
.startCategory(i18n("version").toUpperCase())
.add(gameListItem)
.add(gameItem)
.startCategory(i18n("profile.title").toUpperCase())
.add(profileListItem)
.startCategory(i18n("launcher").toUpperCase())
.add(launcherSettingsItem);
EventBus.EVENT_BUS.channel(RefreshedVersionsEvent.class).register(event -> onRefreshedVersions((HMCLGameRepository) event.getSource()));
Profile profile = Profiles.getSelectedProfile();
if (profile != null && profile.getRepository().isLoaded())
onRefreshedVersions(Profiles.selectedProfileProperty().get().getRepository());
}
// ==== Accounts ====
public void checkAccount() {
if (Accounts.getAccounts().isEmpty())
Platform.runLater(this::addNewAccount);
}
private void addNewAccount() {
Controllers.dialog(new AddAccountPane());
}
// ====
private boolean checkedModpack = false;
private void onRefreshedVersions(HMCLGameRepository repository) {
JFXUtilities.runInFX(() -> {
if (!checkedModpack) {
checkedModpack = true;
if (repository.getVersionCount() == 0) {
File modpackFile = new File("modpack.zip").getAbsoluteFile();
if (modpackFile.exists()) {
Task.supplyAsync(() -> CompressingUtils.findSuitableEncoding(modpackFile.toPath()))
.thenApply(encoding -> ModpackHelper.readModpackManifest(modpackFile.toPath(), encoding))
.thenAccept(modpack -> {
AtomicReference<Region> region = new AtomicReference<>();
TaskExecutor executor = ModpackHelper.getInstallTask(repository.getProfile(), modpackFile, modpack.getName(), modpack)
.withRun(Schedulers.javafx(), this::checkAccount).executor();
region.set(Controllers.taskDialog(executor, i18n("modpack.installing")));
executor.start();
}).start();
}
}
}
checkAccount();
});
}
}
@@ -0,0 +1,80 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
public abstract class ListPage<T extends Node> extends Control {
private final ListProperty<T> items = new SimpleListProperty<>(this, "items", FXCollections.observableArrayList());
private final BooleanProperty loading = new SimpleBooleanProperty(this, "loading", false);
private final BooleanProperty refreshable = new SimpleBooleanProperty(this, "refreshable", false);
public abstract void add();
public void refresh() {
}
@Override
protected Skin<?> createDefaultSkin() {
return new ListPageSkin(this);
}
public ObservableList<T> getItems() {
return items.get();
}
public void setItems(ObservableList<T> items) {
this.items.set(items);
}
public ListProperty<T> itemsProperty() {
return items;
}
public boolean isLoading() {
return loading.get();
}
public void setLoading(boolean loading) {
this.loading.set(loading);
}
public BooleanProperty loadingProperty() {
return loading;
}
public boolean isRefreshable() {
return refreshable.get();
}
public BooleanProperty refreshableProperty() {
return refreshable;
}
public void setRefreshable(boolean refreshable) {
this.refreshable.set(refreshable);
}
}
@@ -0,0 +1,109 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXScrollPane;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
public class ListPageSkin extends SkinBase<ListPage<?>> {
public ListPageSkin(ListPage<?> skinnable) {
super(skinnable);
SpinnerPane spinnerPane = new SpinnerPane();
spinnerPane.getStyleClass().add("large-spinner-pane");
Pane placeholder = new Pane();
StackPane contentPane = new StackPane();
{
ScrollPane scrollPane = new ScrollPane();
{
scrollPane.setFitToWidth(true);
VBox list = new VBox();
list.maxWidthProperty().bind(scrollPane.widthProperty());
list.setSpacing(10);
list.setPadding(new Insets(10));
VBox content = new VBox();
content.getChildren().setAll(list, placeholder);
Bindings.bindContent(list.getChildren(), skinnable.itemsProperty());
scrollPane.setContent(content);
JFXScrollPane.smoothScrolling(scrollPane);
}
VBox vBox = new VBox();
{
vBox.setAlignment(Pos.BOTTOM_RIGHT);
vBox.setPickOnBounds(false);
vBox.setPadding(new Insets(15));
vBox.setSpacing(15);
JFXButton btnAdd = new JFXButton();
FXUtils.setLimitWidth(btnAdd, 40);
FXUtils.setLimitHeight(btnAdd, 40);
btnAdd.getStyleClass().add("jfx-button-raised-round");
btnAdd.setButtonType(JFXButton.ButtonType.RAISED);
btnAdd.setGraphic(SVG.plus(Theme.whiteFillBinding(), -1, -1));
btnAdd.setOnMouseClicked(e -> skinnable.add());
JFXButton btnRefresh = new JFXButton();
FXUtils.setLimitWidth(btnRefresh, 40);
FXUtils.setLimitHeight(btnRefresh, 40);
btnRefresh.getStyleClass().add("jfx-button-raised-round");
btnRefresh.setButtonType(JFXButton.ButtonType.RAISED);
btnRefresh.setGraphic(SVG.refresh(Theme.whiteFillBinding(), -1, -1));
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
vBox.getChildren().setAll(btnAdd);
FXUtils.onChangeAndOperate(skinnable.refreshableProperty(),
refreshable -> {
if (refreshable) vBox.getChildren().setAll(btnRefresh, btnAdd);
else vBox.getChildren().setAll(btnAdd);
});
}
// Keep a blank space to prevent buttons from blocking up mod items.
BorderPane group = new BorderPane();
group.setPickOnBounds(false);
group.setBottom(vBox);
placeholder.minHeightProperty().bind(vBox.heightProperty());
contentPane.getChildren().setAll(scrollPane, group);
}
spinnerPane.loadingProperty().bind(skinnable.loadingProperty());
spinnerPane.setContent(contentPane);
getChildren().setAll(spinnerPane);
}
}
@@ -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) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,413 +13,232 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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 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 javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import org.jackhuang.hmcl.event.Event;
import org.jackhuang.hmcl.event.EventManager;
import org.jackhuang.hmcl.game.LauncherHelper;
import org.jackhuang.hmcl.util.Lang;
import org.jackhuang.hmcl.util.Log4jLevel;
import org.jackhuang.hmcl.util.StringUtils;
import org.jackhuang.hmcl.util.io.IOUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.concurrent.CountDownLatch;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
/**
*
* @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(config().getTheme().getStylesheets());
setTitle(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.setClipboard(this.txtLog.getText());
}//GEN-LAST:event_btnCopyActionPerformed
private void btnContactActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnContactActionPerformed
SwingUtils.openLink(C.URL_CONTACT);
}//GEN-LAST:event_btnContactActionPerformed
private void btnTerminateGameActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnTerminateGameActionPerformed
terminateGames();
}//GEN-LAST:event_btnTerminateGameActionPerformed
private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
if (listener != null && listener.apply())
terminateGames();
SwingUtils.exitIfNoWindow(this);
}//GEN-LAST:event_formWindowClosing
private void cboShowLinesItemStateChanged(java.awt.event.ItemEvent evt) {//GEN-FIRST:event_cboShowLinesItemStateChanged
Settings.getInstance().setLogLines(Integer.parseInt(cboShowLines.getSelectedItem().toString()));
}//GEN-LAST:event_cboShowLinesItemStateChanged
private void txtSearchActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_txtSearchActionPerformed
if (evt.getSource() != txtSearch || txtSearch.getText().isEmpty())
return;
try {
if (!s.equals(txtSearch.getText()))
pos = 0;
StyledDocument d = txtLog.getStyledDocument();
s = txtSearch.getText();
String content = d.getText(0, d.getLength());
int x = content.indexOf(s, pos);
if (x == -1) {
pos = 0;
x = content.indexOf(s, pos);
}
if (x == -1)
return;
int len = s.length();
pos = x + len;
SimpleAttributeSet set = new SimpleAttributeSet(d.getLogicalStyle(x).copyAttributes());
StyleConstants.setBackground(set, txtLog.getSelectionColor());
StyleConstants.setForeground(set, txtLog.getSelectedTextColor());
d.setCharacterAttributes(x, len, set, true);
set = new SimpleAttributeSet(d.getLogicalStyle(lastX).copyAttributes());
StyleConstants.setBackground(set, txtLog.getBackground());
StyleConstants.setForeground(set, txtLog.getForeground());
d.setCharacterAttributes(lastX, lastLen, set, true);
txtLog.select(x, pos);
lastX = x;
lastLen = len;
} catch (BadLocationException e) {
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}", config().getFontSize() + "px \"" + config().getFontFamily() + "\""));
engine.getLoadWorker().stateProperty().addListener((a, b, newValue) -> {
if (newValue == Worker.State.SUCCEEDED) {
document = engine.getDocument();
body = document.getElementsByTagName("body").item(0);
engine.executeScript("limitedLogs=" + config().getLogLines());
latch.countDown();
onDone.fireEvent(new Event(LogWindow.this));
}
} 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(config().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) -> {
config().setLogLines(newValue == null ? 100 : Integer.parseInt(newValue));
engine.executeScript("limitedLogs=" + config().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.concat(fatal, " fatals"));
btnErrors.textProperty().bind(Bindings.concat(error, " errors"));
btnWarns.textProperty().bind(Bindings.concat(warn, " warns"));
btnInfos.textProperty().bind(Bindings.concat(info, " infos"));
btnDebugs.textProperty().bind(Bindings.concat(debug, " debugs"));
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,504 +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) {
int off = enableShadow ? 16 : 0;
int width = this.getWidth();
int height = this.getHeight();
int contentWidth = width - off - off;
int contentHeight = height - off - off;
if (!enableShadow || contentWidth <= 0 || contentHeight <= 0)
paintImpl(g);
else {
BufferedImage contentImage = new BufferedImage(contentWidth,
contentHeight, Transparency.OPAQUE);
Graphics2D contentG2d = contentImage.createGraphics();
contentG2d.translate(-off, -off);
paintImpl(g);
paintImpl(contentG2d);
contentG2d.dispose();
g.drawImage(contentImage, off, off, this);
}
}
public void invokeUpdate() {
defaultTitle = Main.makeTitle() + C.i18n("update.found");
windowTitle.addMouseListener(MouseListenerImpl.INSTANCE);
if (!isShowedMessage) {
windowTitle.setText(defaultTitle);
windowTitle.setForeground(Color.red);
}
}
@Override
public JComponent getRepaintComponent() {
return null;
}
@Override
public Window getRepaintWindow() {
return this;
}
@Override
public Collection<Rectangle> getRepaintRects() {
int off = MainFrame.INSTANCE.enableShadow ? 16 : 0, yoff = MainFrame.INSTANCE.getInsets().top + off, xoff = MainFrame.INSTANCE.getInsets().left + off;
int width = 800, height = MainFrame.INSTANCE.header.getHeight() + 480 - 1;
return Arrays.asList(new Rectangle(xoff, yoff, xoff, height + yoff + 1),
new Rectangle(xoff + width + 1, yoff, xoff + width + 1, height + yoff + 1),
new Rectangle(xoff, height + yoff + 1, xoff + width + 1, height + yoff + 1));
}
private static class MouseListenerImpl implements MouseListener {
public static final MouseListenerImpl INSTANCE = new MouseListenerImpl();
@Override
public void mouseClicked(MouseEvent e) {
Settings.UPDATE_CHECKER.checkOutdate();
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
}
public void failed(String s) {
if (s != null)
MessageBox.show(s);
closeMessage();
}
transient LaunchingUIDaemon daemon = new LaunchingUIDaemon();
}
@@ -0,0 +1,256 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXPopup;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.setting.Profile;
import org.jackhuang.hmcl.setting.Profiles;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.construct.PopupMenu;
import org.jackhuang.hmcl.ui.construct.TwoLineListItem;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.ui.versions.Versions;
import org.jackhuang.hmcl.upgrade.RemoteVersion;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.upgrade.UpdateHandler;
import static org.jackhuang.hmcl.ui.FXUtils.SINE;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public final class MainPage extends StackPane implements DecoratorPage {
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("main_page"));
private final PopupMenu menu = new PopupMenu();
private final JFXPopup popup = new JFXPopup(menu);
private final StringProperty currentGame = new SimpleStringProperty(this, "currentGame");
private final BooleanProperty showUpdate = new SimpleBooleanProperty(this, "showUpdate");
private final StringProperty latestVersion = new SimpleStringProperty(this, "latestVersion");
private final ObservableList<Node> versions = FXCollections.observableArrayList();
private StackPane updatePane;
private JFXButton menuButton;
{
setPadding(new Insets(25));
updatePane = new StackPane();
updatePane.setVisible(false);
updatePane.getStyleClass().add("bubble");
FXUtils.setLimitWidth(updatePane, 230);
FXUtils.setLimitHeight(updatePane, 55);
StackPane.setAlignment(updatePane, Pos.TOP_RIGHT);
updatePane.setOnMouseClicked(e -> onUpgrade());
FXUtils.onChange(showUpdateProperty(), this::doAnimation);
{
HBox hBox = new HBox();
hBox.setSpacing(12);
hBox.setAlignment(Pos.CENTER_LEFT);
StackPane.setAlignment(hBox, Pos.CENTER_LEFT);
StackPane.setMargin(hBox, new Insets(9, 12, 9, 16));
{
Label lblIcon = new Label();
lblIcon.setGraphic(SVG.update(Theme.whiteFillBinding(), 20, 20));
TwoLineListItem prompt = new TwoLineListItem();
prompt.setSubtitle(i18n("update.bubble.subtitle"));
prompt.setPickOnBounds(false);
prompt.titleProperty().bind(latestVersionProperty());
hBox.getChildren().setAll(lblIcon, prompt);
}
JFXButton closeUpdateButton = new JFXButton();
closeUpdateButton.setGraphic(SVG.close(Theme.whiteFillBinding(), 10, 10));
StackPane.setAlignment(closeUpdateButton, Pos.TOP_RIGHT);
closeUpdateButton.getStyleClass().add("toggle-icon-tiny");
StackPane.setMargin(closeUpdateButton, new Insets(5));
closeUpdateButton.setOnMouseClicked(e -> closeUpdateBubble());
updatePane.getChildren().setAll(hBox, closeUpdateButton);
}
StackPane launchPane = new StackPane();
launchPane.setMaxWidth(230);
launchPane.setMaxHeight(55);
StackPane.setAlignment(launchPane, Pos.BOTTOM_RIGHT);
{
JFXButton launchButton = new JFXButton();
launchButton.setPrefWidth(230);
launchButton.setPrefHeight(55);
launchButton.setButtonType(JFXButton.ButtonType.RAISED);
launchButton.getStyleClass().add("jfx-button-raised");
launchButton.setOnMouseClicked(e -> launch());
launchButton.setClip(new Rectangle(-100, -100, 310, 200));
{
VBox graphic = new VBox();
graphic.setAlignment(Pos.CENTER);
graphic.setTranslateX(-7);
graphic.setMaxWidth(200);
Label launchLabel = new Label(i18n("version.launch"));
launchLabel.setStyle("-fx-font-size: 16px;");
Label currentLabel = new Label();
currentLabel.setStyle("-fx-font-size: 12px;");
currentLabel.textProperty().bind(currentGameProperty());
graphic.getChildren().setAll(launchLabel, currentLabel);
launchButton.setGraphic(graphic);
}
Rectangle separator = new Rectangle();
separator.getStyleClass().add("darker-fill");
separator.setWidth(1);
separator.setHeight(57);
separator.setTranslateX(95);
separator.setMouseTransparent(true);
menuButton = new JFXButton();
menuButton.setPrefHeight(55);
menuButton.setPrefWidth(230);
menuButton.setButtonType(JFXButton.ButtonType.RAISED);
menuButton.getStyleClass().add("jfx-button-raised");
menuButton.setStyle("-fx-font-size: 15px;");
menuButton.setOnMouseClicked(e -> onMenu());
menuButton.setClip(new Rectangle(211, -100, 100, 200));
StackPane graphic = new StackPane();
Node svg = SVG.triangle(Theme.whiteFillBinding(), 10, 10);
StackPane.setAlignment(svg, Pos.CENTER_RIGHT);
graphic.getChildren().setAll(svg);
graphic.setTranslateX(12);
menuButton.setGraphic(graphic);
launchPane.getChildren().setAll(launchButton, separator, menuButton);
}
getChildren().setAll(updatePane, launchPane);
menu.setMaxHeight(365);
menu.setMaxWidth(545);
menu.setAlwaysShowingVBar(true);
menu.setOnMouseClicked(e -> popup.hide());
Bindings.bindContent(menu.getContent(), versions);
}
private void doAnimation(boolean show) {
Duration duration = Duration.millis(320);
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(
new KeyFrame(Duration.ZERO,
new KeyValue(updatePane.translateXProperty(), show ? 260 : 0, SINE)),
new KeyFrame(duration,
new KeyValue(updatePane.translateXProperty(), show ? 0 : 260, SINE)));
if (show) nowAnimation.getKeyFrames().add(
new KeyFrame(Duration.ZERO, e -> updatePane.setVisible(true)));
else nowAnimation.getKeyFrames().add(
new KeyFrame(duration, e -> updatePane.setVisible(false)));
nowAnimation.play();
}
private void launch() {
Profile profile = Profiles.getSelectedProfile();
Versions.launch(profile, profile.getSelectedVersion());
}
private void onMenu() {
popup.show(menuButton, JFXPopup.PopupVPosition.BOTTOM, JFXPopup.PopupHPosition.RIGHT, 0, -menuButton.getHeight());
}
private void onUpgrade() {
RemoteVersion target = UpdateChecker.getLatestVersion();
if (target == null) {
return;
}
UpdateHandler.updateFrom(target);
}
private void closeUpdateBubble() {
showUpdate.unbind();
showUpdate.set(false);
}
public String getTitle() {
return title.get();
}
@Override
public ReadOnlyStringProperty titleProperty() {
return title.getReadOnlyProperty();
}
public void setTitle(String title) {
this.title.set(title);
}
public String getCurrentGame() {
return currentGame.get();
}
public StringProperty currentGameProperty() {
return currentGame;
}
public void setCurrentGame(String currentGame) {
this.currentGame.set(currentGame);
}
public boolean isShowUpdate() {
return showUpdate.get();
}
public BooleanProperty showUpdateProperty() {
return showUpdate;
}
public void setShowUpdate(boolean showUpdate) {
this.showUpdate.set(showUpdate);
}
public String getLatestVersion() {
return latestVersion.get();
}
public StringProperty latestVersionProperty() {
return latestVersion;
}
public void setLatestVersion(String latestVersion) {
this.latestVersion.set(latestVersion);
}
public ObservableList<Node> getVersions() {
return versions;
}
}
@@ -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;
};
}
@@ -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;
}
@@ -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,169 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import javafx.beans.binding.ObjectBinding;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Paint;
import javafx.scene.shape.SVGPath;
public final class SVG {
private SVG() {
}
private static Node createSVGPath(String d, ObjectBinding<? extends Paint> fill, double width, double height) {
SVGPath path = new SVGPath();
path.getStyleClass().add("svg");
path.setContent(d);
path.fillProperty().bind(fill);
if (width < 0 || height < 0) {
StackPane pane = new StackPane(path);
pane.setAlignment(Pos.CENTER);
return pane;
}
Group svg = new Group(path);
double scale = Math.min(width / svg.getBoundsInParent().getWidth(), height / svg.getBoundsInParent().getHeight());
svg.setScaleX(scale);
svg.setScaleY(scale);
return svg;
}
// default fill: white, width: 20, height 20
public static Node gear(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z", fill, width, height);
}
public static Node back(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z", fill, width, height);
}
public static Node close(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z", fill, width, height);
}
public static Node dotsVertical(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z", fill, width, height);
}
public static Node delete(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z", fill, width, height);
}
public static Node accountEdit(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M21.7,13.35L20.7,14.35L18.65,12.3L19.65,11.3C19.86,11.09 20.21,11.09 20.42,11.3L21.7,12.58C21.91,12.79 21.91,13.14 21.7,13.35M12,18.94L18.06,12.88L20.11,14.93L14.06,21H12V18.94M12,14C7.58,14 4,15.79 4,18V20H10V18.11L14,14.11C13.34,14.03 12.67,14 12,14M12,4A4,4 0 0,0 8,8A4,4 0 0,0 12,12A4,4 0 0,0 16,8A4,4 0 0,0 12,4Z", fill, width, height);
}
public static Node expand(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M16.59 8.59L12 13.17 7.41 8.59 6 10l6 6 6-6z", fill, width, height);
}
public static Node collapse(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12 8l-6 6 1.41 1.41L12 10.83l4.59 4.58L18 14z", fill, width, height);
}
public static Node navigate(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
}
public static Node launch(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.14311.429 0 20.571 6.286z", fill, width, height);
}
public static Node launch2(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
}
public static Node script(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M14,20A2,2 0 0,0 16,18V5H9A1,1 0 0,0 8,6V16H5V5A3,3 0 0,1 8,2H19A3,3 0 0,1 22,5V6H18V18L18,19A3,3 0 0,1 15,22H5A3,3 0 0,1 2,19V18H12A2,2 0 0,0 14,20Z", fill, width, height);
}
public static Node pencil(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z", fill, width, height);
}
public static Node refresh(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z", fill, width, height);
}
public static Node folderOpen(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,20H4C2.89,20 2,19.1 2,18V6C2,4.89 2.89,4 4,4H10L12,6H19A2,2 0 0,1 21,8H21L4,8V18L6.14,10H23.21L20.93,18.5C20.7,19.37 19.92,20 19,20Z", fill, width, height);
}
public static Node update(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z", fill, width, height);
}
public static Node closeCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M15.59,7L12,10.59L8.41,7L7,8.41L10.59,12L7,15.59L8.41,17L12,13.41L15.59,17L17,15.59L13.41,12L17,8.41L15.59,7Z", fill, width, height);
}
public static Node checkCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M11,16.5L18,9.5L16.59,8.09L11,13.67L7.91,10.59L6.5,12L11,16.5Z", fill, width, height);
}
public static Node infoCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M13,9H11V7H13M13,17H11V11H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z", fill, width, height);
}
public static Node helpCircle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M15.07,11.25L14.17,12.17C13.45,12.89 13,13.5 13,15H11V14.5C11,13.39 11.45,12.39 12.17,11.67L13.41,10.41C13.78,10.05 14,9.55 14,9C14,7.89 13.1,7 12,7A2,2 0 0,0 10,9H8A4,4 0 0,1 12,5A4,4 0 0,1 16,9C16,9.88 15.64,10.67 15.07,11.25M13,19H11V17H13M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z", fill, width, height);
}
public static Node alert(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z", fill, width, height);
}
public static Node plus(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z", fill, width, height);
}
public static Node importIcon(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M14,12L10,8V11H2V13H10V16M20,18V6C20,4.89 19.1,4 18,4H6A2,2 0 0,0 4,6V9H6V6H18V18H6V15H4V18A2,2 0 0,0 6,20H18A2,2 0 0,0 20,18Z", fill, width, height);
}
public static Node export(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M23,12L19,8V11H10V13H19V16M1,18V6C1,4.89 1.9,4 3,4H15A2,2 0 0,1 17,6V9H15V6H3V18H15V15H17V18A2,2 0 0,1 15,20H3A2,2 0 0,1 1,18Z", fill, width, height);
}
public static Node openInNew(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z", fill, width, height);
}
public static Node triangle(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M1,21H23L12,2", fill, width, height);
}
public static Node home(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z", fill, width, height);
}
public static Node viewList(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M7,5H21V7H7V5M7,13V11H21V13H7M4,4.5A1.5,1.5 0 0,1 5.5,6A1.5,1.5 0 0,1 4,7.5A1.5,1.5 0 0,1 2.5,6A1.5,1.5 0 0,1 4,4.5M4,10.5A1.5,1.5 0 0,1 5.5,12A1.5,1.5 0 0,1 4,13.5A1.5,1.5 0 0,1 2.5,12A1.5,1.5 0 0,1 4,10.5M7,19V17H21V19H7M4,16.5A1.5,1.5 0 0,1 5.5,18A1.5,1.5 0 0,1 4,19.5A1.5,1.5 0 0,1 2.5,18A1.5,1.5 0 0,1 4,16.5Z", fill, width, height);
}
public static Node check(ObjectBinding<? extends Paint> fill, double width, double height) {
return createSVGPath("M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z", fill, width, height);
}
}
@@ -0,0 +1,255 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.JFXColorPicker;
import com.jfoenix.effects.JFXDepthManager;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.When;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.control.ToggleGroup;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import org.jackhuang.hmcl.Metadata;
import org.jackhuang.hmcl.setting.*;
import org.jackhuang.hmcl.ui.construct.MessageDialogPane.MessageType;
import org.jackhuang.hmcl.ui.construct.Validator;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.upgrade.RemoteVersion;
import org.jackhuang.hmcl.upgrade.UpdateChannel;
import org.jackhuang.hmcl.upgrade.UpdateChecker;
import org.jackhuang.hmcl.upgrade.UpdateHandler;
import org.jackhuang.hmcl.util.Logging;
import org.jackhuang.hmcl.util.i18n.Locales;
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
import java.awt.*;
import java.io.IOException;
import java.net.Proxy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.logging.Level;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.reservedSelectedPropertyFor;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.selectedItemPropertyFor;
public final class SettingsPage extends SettingsView implements DecoratorPage {
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("settings.launcher"));
private InvalidationListener updateListener;
public SettingsPage() {
FXUtils.smoothScrolling(scroll);
// ==== Download sources ====
cboDownloadSource.getItems().setAll(DownloadProviders.providersById.keySet());
selectedItemPropertyFor(cboDownloadSource).bindBidirectional(config().downloadTypeProperty());
// ====
// ==== Font ====
cboFont.valueProperty().bindBidirectional(config().fontFamilyProperty());
txtFontSize.textProperty().bindBidirectional(config().fontSizeProperty(),
SafeStringConverter.fromFiniteDouble()
.restrict(it -> it > 0)
.fallbackTo(12.0)
.asPredicate(Validator.addTo(txtFontSize)));
lblDisplay.fontProperty().bind(Bindings.createObjectBinding(
() -> Font.font(config().getFontFamily(), config().getFontSize()),
config().fontFamilyProperty(), config().fontSizeProperty()));
// ====
// ==== Languages ====
cboLanguage.getItems().setAll(Locales.LOCALES);
selectedItemPropertyFor(cboLanguage).bindBidirectional(config().localizationProperty());
// ====
// ==== Proxy ====
txtProxyHost.textProperty().bindBidirectional(config().proxyHostProperty());
txtProxyPort.textProperty().bindBidirectional(config().proxyPortProperty(),
SafeStringConverter.fromInteger()
.restrict(it -> it >= 0 && it <= 0xFFFF)
.fallbackTo(0)
.asPredicate(Validator.addTo(txtProxyPort)));
txtProxyUsername.textProperty().bindBidirectional(config().proxyUserProperty());
txtProxyPassword.textProperty().bindBidirectional(config().proxyPassProperty());
proxyPane.disableProperty().bind(chkDisableProxy.selectedProperty());
authPane.disableProperty().bind(chkProxyAuthentication.selectedProperty().not());
reservedSelectedPropertyFor(chkDisableProxy).bindBidirectional(config().hasProxyProperty());
chkProxyAuthentication.selectedProperty().bindBidirectional(config().hasProxyAuthProperty());
ToggleGroup proxyConfigurationGroup = new ToggleGroup();
chkProxyHttp.setUserData(Proxy.Type.HTTP);
chkProxyHttp.setToggleGroup(proxyConfigurationGroup);
chkProxySocks.setUserData(Proxy.Type.SOCKS);
chkProxySocks.setToggleGroup(proxyConfigurationGroup);
selectedItemPropertyFor(proxyConfigurationGroup, Proxy.Type.class).bindBidirectional(config().proxyTypeProperty());
// ====
fileCommonLocation.loadChildren(Arrays.asList(
fileCommonLocation.createChildren(i18n("launcher.cache_directory.default"), EnumCommonDirectory.DEFAULT)
), EnumCommonDirectory.CUSTOM);
fileCommonLocation.selectedDataProperty().bindBidirectional(config().commonDirTypeProperty());
fileCommonLocation.customTextProperty().bindBidirectional(config().commonDirectoryProperty());
fileCommonLocation.subtitleProperty().bind(
Bindings.createObjectBinding(() -> Optional.ofNullable(Settings.instance().getCommonDirectory())
.orElse(i18n("launcher.cache_directory.disabled")),
config().commonDirectoryProperty(), config().commonDirTypeProperty()));
// ==== Update ====
FXUtils.installFastTooltip(btnUpdate, i18n("update.tooltip"));
updateListener = any -> {
btnUpdate.setVisible(UpdateChecker.isOutdated());
if (UpdateChecker.isOutdated()) {
lblUpdateSub.setText(i18n("update.newest_version", UpdateChecker.getLatestVersion().getVersion()));
lblUpdateSub.getStyleClass().setAll("update-label");
lblUpdate.setText(i18n("update.found"));
lblUpdate.getStyleClass().setAll("update-label");
} else if (UpdateChecker.isCheckingUpdate()) {
lblUpdateSub.setText(i18n("update.checking"));
lblUpdateSub.getStyleClass().setAll("subtitle-label");
lblUpdate.setText(i18n("update"));
lblUpdate.getStyleClass().setAll();
} else {
lblUpdateSub.setText(i18n("update.latest"));
lblUpdateSub.getStyleClass().setAll("subtitle-label");
lblUpdate.setText(i18n("update"));
lblUpdate.getStyleClass().setAll();
}
};
UpdateChecker.latestVersionProperty().addListener(new WeakInvalidationListener(updateListener));
UpdateChecker.outdatedProperty().addListener(new WeakInvalidationListener(updateListener));
UpdateChecker.checkingUpdateProperty().addListener(new WeakInvalidationListener(updateListener));
updateListener.invalidated(null);
lblUpdateNote.setWrappingWidth(470);
ToggleGroup updateChannelGroup = new ToggleGroup();
chkUpdateDev.setToggleGroup(updateChannelGroup);
chkUpdateDev.setUserData(UpdateChannel.DEVELOPMENT);
chkUpdateStable.setToggleGroup(updateChannelGroup);
chkUpdateStable.setUserData(UpdateChannel.STABLE);
selectedItemPropertyFor(updateChannelGroup, UpdateChannel.class).bindBidirectional(config().updateChannelProperty());
// ====
// ==== Background ====
backgroundItem.loadChildren(Collections.singletonList(
backgroundItem.createChildren(i18n("launcher.background.default"), EnumBackgroundImage.DEFAULT)
), EnumBackgroundImage.CUSTOM);
backgroundItem.customTextProperty().bindBidirectional(config().backgroundImageProperty());
backgroundItem.selectedDataProperty().bindBidirectional(config().backgroundImageTypeProperty());
backgroundItem.subtitleProperty().bind(
new When(backgroundItem.selectedDataProperty().isEqualTo(EnumBackgroundImage.DEFAULT))
.then(i18n("launcher.background.default"))
.otherwise(config().backgroundImageProperty()));
// ====
// ==== Theme ====
JFXColorPicker picker = new JFXColorPicker(Color.web(config().getTheme().getColor()), null);
picker.setCustomColorText(i18n("color.custom"));
picker.setRecentColorsText(i18n("color.recent"));
picker.getCustomColors().setAll(Theme.SUGGESTED_COLORS);
picker.setOnAction(e -> {
Theme theme = Theme.custom(Theme.getColorDisplayName(picker.getValue()));
config().setTheme(theme);
Controllers.getScene().getStylesheets().setAll(theme.getStylesheets());
});
themeColorPickerContainer.getChildren().setAll(picker);
Platform.runLater(() -> JFXDepthManager.setDepth(picker, 0));
// ====
}
public String getTitle() {
return title.get();
}
@Override
public ReadOnlyStringProperty titleProperty() {
return title.getReadOnlyProperty();
}
public void setTitle(String title) {
this.title.set(title);
}
@Override
protected void onUpdate() {
RemoteVersion target = UpdateChecker.getLatestVersion();
if (target == null) {
return;
}
UpdateHandler.updateFrom(target);
}
@Override
protected void onExportLogs() {
// We cannot determine which file is JUL using.
// So we write all the logs to a new file.
thread(() -> {
Path logFile = Paths.get("hmcl-exported-logs-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss")) + ".log").toAbsolutePath();
LOG.info("Exporting logs to " + logFile);
try {
Files.write(logFile, Logging.getRawLogs());
} catch (IOException e) {
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.failed") + "\n" + e, null, MessageType.ERROR));
LOG.log(Level.WARNING, "Failed to export logs", e);
return;
}
Platform.runLater(() -> Controllers.dialog(i18n("settings.launcher.launcher_log.export.success", logFile)));
if (Desktop.isDesktopSupported()) {
try {
Desktop.getDesktop().open(logFile.toFile());
} catch (IOException ignored) {
}
}
});
}
@Override
protected void onHelp() {
FXUtils.openLink(Metadata.HELP_URL);
}
@Override
protected void onSponsor() {
FXUtils.openLink("https://hmcl.huangyuhui.net/api/redirect/sponsor");
}
}
@@ -0,0 +1,509 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import com.jfoenix.controls.*;
import javafx.geometry.HPos;
import javafx.geometry.Pos;
import javafx.geometry.VPos;
import javafx.scene.Cursor;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.*;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import org.jackhuang.hmcl.setting.EnumBackgroundImage;
import org.jackhuang.hmcl.setting.EnumCommonDirectory;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.i18n.Locales.SupportedLocale;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.stringConverter;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public abstract class SettingsView extends StackPane {
protected final JFXTextField txtProxyHost;
protected final JFXTextField txtProxyPort;
protected final JFXTextField txtProxyUsername;
protected final JFXPasswordField txtProxyPassword;
protected final JFXTextField txtFontSize;
protected final JFXComboBox<SupportedLocale> cboLanguage;
protected final JFXComboBox<String> cboDownloadSource;
protected final FontComboBox cboFont;
protected final MultiFileItem<EnumCommonDirectory> fileCommonLocation;
protected final Label lblDisplay;
protected final Label lblUpdate;
protected final Label lblUpdateSub;
protected final Text lblUpdateNote;
protected final JFXRadioButton chkUpdateStable;
protected final JFXRadioButton chkUpdateDev;
protected final JFXButton btnUpdate;
protected final ScrollPane scroll;
protected final MultiFileItem<EnumBackgroundImage> backgroundItem;
protected final StackPane themeColorPickerContainer;
protected final JFXCheckBox chkDisableProxy;
protected final JFXRadioButton chkProxyHttp;
protected final JFXRadioButton chkProxySocks;
protected final JFXCheckBox chkProxyAuthentication;
protected final GridPane authPane;
protected final Pane proxyPane;
public SettingsView() {
scroll = new ScrollPane();
getChildren().setAll(scroll);
scroll.setStyle("-fx-font-size: 14;");
scroll.setFitToWidth(true);
{
VBox rootPane = new VBox();
rootPane.setStyle("-fx-padding: 18;");
{
ComponentList settingsPane = new ComponentList();
{
{
StackPane sponsorPane = new StackPane();
sponsorPane.setCursor(Cursor.HAND);
sponsorPane.setOnMouseClicked(e -> onSponsor());
{
Label label = new Label(i18n("sponsor.hmcl"));
label.setWrapText(true);
label.prefWidthProperty().bind(sponsorPane.widthProperty());
sponsorPane.getChildren().add(label);
}
settingsPane.getContent().add(sponsorPane);
}
ComponentSublist updatePane = new ComponentSublist();
updatePane.setTitle(i18n("update"));
updatePane.setHasSubtitle(true);
{
VBox headerLeft = new VBox();
lblUpdate = new Label(i18n("update"));
lblUpdateSub = new Label();
lblUpdateSub.getStyleClass().add("subtitle-label");
headerLeft.getChildren().setAll(lblUpdate, lblUpdateSub);
updatePane.setHeaderLeft(headerLeft);
}
{
btnUpdate = new JFXButton();
btnUpdate.setOnMouseClicked(e -> onUpdate());
btnUpdate.getStyleClass().add("toggle-icon4");
btnUpdate.setGraphic(SVG.update(Theme.blackFillBinding(), 20, 20));
updatePane.setHeaderRight(btnUpdate);
}
{
VBox content = new VBox();
content.setSpacing(8);
chkUpdateStable = new JFXRadioButton(i18n("update.channel.stable"));
chkUpdateDev = new JFXRadioButton(i18n("update.channel.dev"));
VBox noteWrapper = new VBox();
noteWrapper.setStyle("-fx-padding: 10 0 0 0;");
lblUpdateNote = new Text(i18n("update.note"));
noteWrapper.getChildren().setAll(lblUpdateNote);
content.getChildren().setAll(chkUpdateStable, chkUpdateDev, noteWrapper);
updatePane.getContent().add(content);
}
settingsPane.getContent().add(updatePane);
}
{
BorderPane updatePane = new BorderPane();
{
VBox headerLeft = new VBox();
Label help = new Label(i18n("help"));
Label helpSubtitle = new Label(i18n("help.detail"));
helpSubtitle.getStyleClass().add("subtitle-label");
headerLeft.getChildren().setAll(help, helpSubtitle);
updatePane.setLeft(headerLeft);
}
{
JFXButton btnExternal = new JFXButton();
btnExternal.setOnMouseClicked(e -> onHelp());
btnExternal.getStyleClass().add("toggle-icon4");
btnExternal.setGraphic(SVG.openInNew(Theme.blackFillBinding(), -1, -1));
updatePane.setRight(btnExternal);
}
settingsPane.getContent().add(updatePane);
}
{
fileCommonLocation = new MultiFileItem<>(true);
fileCommonLocation.setTitle(i18n("launcher.cache_directory"));
fileCommonLocation.setDirectory(true);
fileCommonLocation.setChooserTitle(i18n("launcher.cache_directory.choose"));
fileCommonLocation.setHasSubtitle(true);
fileCommonLocation.setCustomText("settings.custom");
settingsPane.getContent().add(fileCommonLocation);
}
{
backgroundItem = new MultiFileItem<>(true);
backgroundItem.setTitle(i18n("launcher.background"));
backgroundItem.setChooserTitle(i18n("launcher.background.choose"));
backgroundItem.setHasSubtitle(true);
backgroundItem.setCustomText(i18n("settings.custom"));
settingsPane.getContent().add(backgroundItem);
}
{
BorderPane downloadSourcePane = new BorderPane();
{
Label label = new Label(i18n("settings.launcher.download_source"));
BorderPane.setAlignment(label, Pos.CENTER_LEFT);
downloadSourcePane.setLeft(label);
}
{
cboDownloadSource = new JFXComboBox<>();
cboDownloadSource.setConverter(stringConverter(key -> i18n("download.provider." + key)));
downloadSourcePane.setRight(cboDownloadSource);
}
settingsPane.getContent().add(downloadSourcePane);
}
{
BorderPane languagePane = new BorderPane();
Label left = new Label(i18n("settings.launcher.language"));
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
languagePane.setLeft(left);
cboLanguage = new JFXComboBox<>();
cboLanguage.setConverter(stringConverter(locale -> locale.getName(config().getLocalization().getResourceBundle())));
FXUtils.setLimitWidth(cboLanguage, 400);
languagePane.setRight(cboLanguage);
settingsPane.getContent().add(languagePane);
}
{
ComponentList proxyList = new ComponentList();
proxyList.setTitle(i18n("settings.launcher.proxy"));
VBox proxyWrapper = new VBox();
proxyWrapper.setSpacing(10);
{
chkDisableProxy = new JFXCheckBox(i18n("settings.launcher.proxy.disable"));
proxyWrapper.getChildren().add(chkDisableProxy);
}
{
proxyPane = new VBox();
proxyPane.setStyle("-fx-padding: 0 0 0 30;");
ColumnConstraints colHgrow = new ColumnConstraints();
colHgrow.setHgrow(Priority.ALWAYS);
{
HBox hBox = new HBox();
chkProxyHttp = new JFXRadioButton(i18n("settings.launcher.proxy.http"));
chkProxySocks = new JFXRadioButton(i18n("settings.launcher.proxy.socks"));
hBox.getChildren().setAll(chkProxyHttp, chkProxySocks);
proxyPane.getChildren().add(hBox);
}
{
GridPane gridPane = new GridPane();
gridPane.setHgap(20);
gridPane.setVgap(10);
gridPane.setStyle("-fx-padding: 0 0 0 15;");
gridPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
gridPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
{
Label host = new Label(i18n("settings.launcher.proxy.host"));
GridPane.setRowIndex(host, 1);
GridPane.setColumnIndex(host, 0);
GridPane.setHalignment(host, HPos.RIGHT);
gridPane.getChildren().add(host);
}
{
txtProxyHost = new JFXTextField();
GridPane.setRowIndex(txtProxyHost, 1);
GridPane.setColumnIndex(txtProxyHost, 1);
gridPane.getChildren().add(txtProxyHost);
}
{
Label port = new Label(i18n("settings.launcher.proxy.port"));
GridPane.setRowIndex(port, 2);
GridPane.setColumnIndex(port, 0);
GridPane.setHalignment(port, HPos.RIGHT);
gridPane.getChildren().add(port);
}
{
txtProxyPort = new JFXTextField();
GridPane.setRowIndex(txtProxyPort, 2);
GridPane.setColumnIndex(txtProxyPort, 1);
FXUtils.setValidateWhileTextChanged(txtProxyPort, true);
txtProxyHost.getValidators().setAll(new NumberValidator(i18n("input.number"), false));
gridPane.getChildren().add(txtProxyPort);
}
proxyPane.getChildren().add(gridPane);
}
{
VBox vBox = new VBox();
vBox.setStyle("-fx-padding: 20 0 20 5;");
chkProxyAuthentication = new JFXCheckBox(i18n("settings.launcher.proxy.authentication"));
vBox.getChildren().setAll(chkProxyAuthentication);
proxyPane.getChildren().add(vBox);
}
{
authPane = new GridPane();
authPane.setHgap(20);
authPane.setVgap(10);
authPane.setStyle("-fx-padding: 0 0 0 15;");
authPane.getColumnConstraints().setAll(new ColumnConstraints(), colHgrow);
authPane.getRowConstraints().setAll(new RowConstraints(), new RowConstraints());
{
Label username = new Label(i18n("settings.launcher.proxy.username"));
GridPane.setRowIndex(username, 0);
GridPane.setColumnIndex(username, 0);
authPane.getChildren().add(username);
}
{
txtProxyUsername = new JFXTextField();
GridPane.setRowIndex(txtProxyUsername, 0);
GridPane.setColumnIndex(txtProxyUsername, 1);
authPane.getChildren().add(txtProxyUsername);
}
{
Label password = new Label(i18n("settings.launcher.proxy.password"));
GridPane.setRowIndex(password, 1);
GridPane.setColumnIndex(password, 0);
authPane.getChildren().add(password);
}
{
txtProxyPassword = new JFXPasswordField();
GridPane.setRowIndex(txtProxyPassword, 1);
GridPane.setColumnIndex(txtProxyPassword, 1);
authPane.getChildren().add(txtProxyPassword);
}
proxyPane.getChildren().add(authPane);
}
proxyWrapper.getChildren().add(proxyPane);
}
proxyList.getContent().add(proxyWrapper);
settingsPane.getContent().add(proxyList);
}
{
BorderPane themePane = new BorderPane();
Label left = new Label(i18n("settings.launcher.theme"));
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
themePane.setLeft(left);
themeColorPickerContainer = new StackPane();
themeColorPickerContainer.setMinHeight(30);
themePane.setRight(themeColorPickerContainer);
settingsPane.getContent().add(themePane);
}
{
ComponentSublist logPane = new ComponentSublist();
logPane.setTitle(i18n("settings.launcher.log"));
{
JFXButton logButton = new JFXButton(i18n("settings.launcher.launcher_log.export"));
logButton.setOnMouseClicked(e -> onExportLogs());
logButton.getStyleClass().add("jfx-button-border");
logPane.setHeaderRight(logButton);
}
{
VBox fontPane = new VBox();
fontPane.setSpacing(5);
{
BorderPane borderPane = new BorderPane();
fontPane.getChildren().add(borderPane);
{
Label left = new Label(i18n("settings.launcher.log.font"));
BorderPane.setAlignment(left, Pos.CENTER_LEFT);
borderPane.setLeft(left);
}
{
HBox hBox = new HBox();
hBox.setSpacing(3);
cboFont = new FontComboBox(12);
txtFontSize = new JFXTextField();
FXUtils.setLimitWidth(txtFontSize, 50);
hBox.getChildren().setAll(cboFont, txtFontSize);
borderPane.setRight(hBox);
}
}
lblDisplay = new Label("[23:33:33] [Client Thread/INFO] [WaterPower]: Loaded mod WaterPower.");
fontPane.getChildren().add(lblDisplay);
logPane.getContent().add(fontPane);
}
settingsPane.getContent().add(logPane);
}
{
StackPane aboutPane = new StackPane();
GridPane gridPane = new GridPane();
gridPane.setHgap(20);
gridPane.setVgap(10);
ColumnConstraints col1 = new ColumnConstraints();
col1.setHgrow(Priority.SOMETIMES);
col1.setMaxWidth(Double.NEGATIVE_INFINITY);
col1.setMinWidth(Double.NEGATIVE_INFINITY);
ColumnConstraints col2 = new ColumnConstraints();
col2.setHgrow(Priority.SOMETIMES);
col2.setMinWidth(20);
col2.setMaxWidth(Double.POSITIVE_INFINITY);
gridPane.getColumnConstraints().setAll(col1, col2);
RowConstraints row = new RowConstraints();
row.setMinHeight(Double.NEGATIVE_INFINITY);
row.setValignment(VPos.TOP);
row.setVgrow(Priority.SOMETIMES);
gridPane.getRowConstraints().setAll(row, row, row, row, row, row);
{
Label label = new Label(i18n("about.copyright"));
GridPane.setRowIndex(label, 0);
GridPane.setColumnIndex(label, 0);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.copyright.statement"));
label.setWrapText(true);
GridPane.setRowIndex(label, 0);
GridPane.setColumnIndex(label, 1);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.author"));
GridPane.setRowIndex(label, 1);
GridPane.setColumnIndex(label, 0);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.author.statement"));
label.setWrapText(true);
GridPane.setRowIndex(label, 1);
GridPane.setColumnIndex(label, 1);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.thanks_to"));
GridPane.setRowIndex(label, 2);
GridPane.setColumnIndex(label, 0);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.thanks_to.statement"));
label.setWrapText(true);
GridPane.setRowIndex(label, 2);
GridPane.setColumnIndex(label, 1);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.dependency"));
GridPane.setRowIndex(label, 3);
GridPane.setColumnIndex(label, 0);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.dependency.statement"));
label.setWrapText(true);
GridPane.setRowIndex(label, 3);
GridPane.setColumnIndex(label, 1);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.claim"));
GridPane.setRowIndex(label, 4);
GridPane.setColumnIndex(label, 0);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.claim.statement"));
label.setWrapText(true);
label.setTextAlignment(TextAlignment.JUSTIFY);
GridPane.setRowIndex(label, 4);
GridPane.setColumnIndex(label, 1);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.open_source"));
GridPane.setRowIndex(label, 5);
GridPane.setColumnIndex(label, 0);
gridPane.getChildren().add(label);
}
{
Label label = new Label(i18n("about.open_source.statement"));
label.setWrapText(true);
GridPane.setRowIndex(label, 5);
GridPane.setColumnIndex(label, 1);
gridPane.getChildren().add(label);
}
aboutPane.getChildren().setAll(gridPane);
settingsPane.getContent().add(aboutPane);
}
rootPane.getChildren().add(settingsPane);
}
scroll.setContent(rootPane);
}
}
protected abstract void onUpdate();
protected abstract void onHelp();
protected abstract void onExportLogs();
protected abstract void onSponsor();
}
@@ -0,0 +1,58 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.WeakChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.WeakListChangeListener;
import java.util.LinkedList;
import java.util.List;
public class WeakListenerHolder {
private List<Object> refs = new LinkedList<>();
public WeakListenerHolder() {
}
public WeakInvalidationListener weak(InvalidationListener listener) {
refs.add(listener);
return new WeakInvalidationListener(listener);
}
public <T> WeakChangeListener<T> weak(ChangeListener<T> listener) {
refs.add(listener);
return new WeakChangeListener<>(listener);
}
public <T> WeakListChangeListener<T> weak(ListChangeListener<T> listener) {
refs.add(listener);
return new WeakListChangeListener<>(listener);
}
public void add(Object obj) {
refs.add(obj);
}
public boolean remove(Object obj) {
return refs.remove(obj);
}
}
@@ -0,0 +1,39 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
public class WebStage extends Stage {
private final WebView webView = new WebView();
public WebStage() {
setScene(new Scene(webView, 800, 480));
getScene().getStylesheets().addAll(config().getTheme().getStylesheets());
getIcons().add(new Image("/assets/img/icon.png"));
}
public WebView getWebView() {
return webView;
}
}
@@ -0,0 +1,70 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.ui.construct.AdvancedListItem;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AccountAdvancedListItem extends AdvancedListItem {
private ObjectProperty<Account> account = new SimpleObjectProperty<Account>() {
@Override
protected void invalidated() {
Account account = get();
if (account == null) {
titleProperty().unbind();
setTitle(i18n("account.missing"));
setSubtitle(i18n("account.missing.add"));
imageProperty().unbind();
setImage(new Image("/assets/img/craft_table.png"));
} else {
titleProperty().bind(Bindings.createStringBinding(account::getCharacter, account));
setSubtitle(accountSubtitle(account));
imageProperty().bind(TexturesLoader.fxAvatarBinding(account, 32));
}
}
};
public AccountAdvancedListItem() {
setRightGraphic(SVG.viewList(Theme.blackFillBinding(), -1, -1));
}
public ObjectProperty<Account> accountProperty() {
return account;
}
private static String accountSubtitle(Account account) {
if (account instanceof OfflineAccount)
return i18n("account.methods.offline");
else if (account instanceof YggdrasilAccount)
return account.getUsername();
else
return "";
}
}
@@ -0,0 +1,57 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.ListPage;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import static org.jackhuang.hmcl.util.javafx.ExtendedProperties.createSelectedItemPropertyFor;
public class AccountList extends ListPage<AccountListItem> implements DecoratorPage {
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.manage"));
private final ListProperty<Account> accounts = new SimpleListProperty<>(this, "accounts", FXCollections.observableArrayList());
private final ObjectProperty<Account> selectedAccount;
public AccountList() {
setItems(MappedObservableList.create(accounts, AccountListItem::new));
selectedAccount = createSelectedItemPropertyFor(getItems(), Account.class);
}
public ObjectProperty<Account> selectedAccountProperty() {
return selectedAccount;
}
public ListProperty<Account> accountsProperty() {
return accounts;
}
@Override
public void add() {
Controllers.dialog(new AddAccountPane());
}
@Override
public ReadOnlyStringProperty titleProperty() {
return title.getReadOnlyProperty();
}
}
@@ -0,0 +1,142 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.*;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthenticationException;
import org.jackhuang.hmcl.auth.CredentialExpiredException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.offline.OfflineAccount;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.ui.DialogController;
import static org.jackhuang.hmcl.util.Lang.thread;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
public class AccountListItem extends RadioButton {
private final Account account;
private final StringProperty title = new SimpleStringProperty();
private final StringProperty subtitle = new SimpleStringProperty();
private final ObjectProperty<Image> image = new SimpleObjectProperty<>();
public AccountListItem(Account account) {
this.account = account;
getStyleClass().clear();
setUserData(account);
String loginTypeName = Accounts.getLocalizedLoginTypeName(Accounts.getAccountFactory(account));
if (account instanceof AuthlibInjectorAccount) {
AuthlibInjectorServer server = ((AuthlibInjectorAccount) account).getServer();
subtitle.bind(Bindings.concat(
loginTypeName, ", ", i18n("account.injector.server"), ": ",
Bindings.createStringBinding(server::getName, server)));
} else {
subtitle.set(loginTypeName);
}
StringBinding characterName = Bindings.createStringBinding(account::getCharacter, account);
if (account instanceof OfflineAccount) {
title.bind(characterName);
} else {
title.bind(Bindings.concat(account.getUsername(), " - ", characterName));
}
image.bind(TexturesLoader.fxAvatarBinding(account, 32));
}
@Override
protected Skin<?> createDefaultSkin() {
return new AccountListItemSkin(this);
}
public void refresh() {
account.clearCache();
thread(() -> {
try {
account.logIn();
} catch (CredentialExpiredException e) {
try {
DialogController.logIn(account);
} catch (CancellationException e1) {
// ignore cancellation
} catch (Exception e1) {
LOG.log(Level.WARNING, "Failed to refresh " + account + " with password", e1);
}
} catch (AuthenticationException e) {
LOG.log(Level.WARNING, "Failed to refresh " + account + " with token", e);
}
});
}
public void remove() {
Accounts.getAccounts().remove(account);
}
public Account getAccount() {
return account;
}
public String getTitle() {
return title.get();
}
public void setTitle(String title) {
this.title.set(title);
}
public StringProperty titleProperty() {
return title;
}
public String getSubtitle() {
return subtitle.get();
}
public void setSubtitle(String subtitle) {
this.subtitle.set(subtitle);
}
public StringProperty subtitleProperty() {
return subtitle;
}
public Image getImage() {
return image.get();
}
public void setImage(Image image) {
this.image.set(image);
}
public ObjectProperty<Image> imageProperty() {
return image;
}
}
@@ -0,0 +1,111 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import com.jfoenix.utils.JFXUtilities;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXRadioButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorAccount;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
public class AccountListItemSkin extends SkinBase<AccountListItem> {
public AccountListItemSkin(AccountListItem skinnable) {
super(skinnable);
BorderPane root = new BorderPane();
JFXRadioButton chkSelected = new JFXRadioButton() {
@Override
public void fire() {
skinnable.fire();
}
};
BorderPane.setAlignment(chkSelected, Pos.CENTER);
chkSelected.selectedProperty().bind(skinnable.selectedProperty());
root.setLeft(chkSelected);
HBox center = new HBox();
center.setSpacing(8);
center.setAlignment(Pos.CENTER_LEFT);
ImageView imageView = new ImageView();
FXUtils.limitSize(imageView, 32, 32);
imageView.imageProperty().bind(skinnable.imageProperty());
Label title = new Label();
title.getStyleClass().add("title");
title.textProperty().bind(skinnable.titleProperty());
Label subtitle = new Label();
subtitle.getStyleClass().add("subtitle");
subtitle.textProperty().bind(skinnable.subtitleProperty());
if (skinnable.getAccount() instanceof AuthlibInjectorAccount) {
Tooltip tooltip = new Tooltip();
AuthlibInjectorServer server = ((AuthlibInjectorAccount) skinnable.getAccount()).getServer();
tooltip.textProperty().bind(BindingMapping.of(server, AuthlibInjectorServer::toString));
FXUtils.installSlowTooltip(subtitle, tooltip);
}
VBox item = new VBox(title, subtitle);
item.getStyleClass().add("two-line-list-item");
BorderPane.setAlignment(item, Pos.CENTER);
center.getChildren().setAll(imageView, item);
root.setCenter(center);
HBox right = new HBox();
right.setAlignment(Pos.CENTER_RIGHT);
JFXButton btnRefresh = new JFXButton();
btnRefresh.setOnMouseClicked(e -> skinnable.refresh());
btnRefresh.getStyleClass().add("toggle-icon4");
btnRefresh.setGraphic(SVG.refresh(Theme.blackFillBinding(), -1, -1));
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRefresh, i18n("button.refresh")));
right.getChildren().add(btnRefresh);
JFXButton btnRemove = new JFXButton();
btnRemove.setOnMouseClicked(e -> skinnable.remove());
btnRemove.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(btnRemove, Pos.CENTER);
btnRemove.setGraphic(SVG.delete(Theme.blackFillBinding(), -1, -1));
JFXUtilities.runInFX(() -> FXUtils.installFastTooltip(btnRemove, i18n("button.delete")));
right.getChildren().add(btnRemove);
root.setRight(right);
root.setStyle("-fx-background-color: white; -fx-padding: 8 8 8 12;");
JFXDepthManager.setDepth(root, 1);
getChildren().setAll(root);
}
}
@@ -0,0 +1,85 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXPasswordField;
import com.jfoenix.controls.JFXProgressBar;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.auth.Account;
import org.jackhuang.hmcl.auth.AuthInfo;
import org.jackhuang.hmcl.auth.NoSelectedCharacterException;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import java.util.function.Consumer;
import java.util.logging.Level;
import static org.jackhuang.hmcl.util.Logging.LOG;
public class AccountLoginPane extends StackPane {
private final Account oldAccount;
private final Consumer<AuthInfo> success;
private final Runnable failed;
@FXML private Label lblUsername;
@FXML private JFXPasswordField txtPassword;
@FXML private Label lblCreationWarning;
@FXML private JFXProgressBar progressBar;
public AccountLoginPane(Account oldAccount, Consumer<AuthInfo> success, Runnable failed) {
this.oldAccount = oldAccount;
this.success = success;
this.failed = failed;
FXUtils.loadFXML(this, "/assets/fxml/account-login.fxml");
lblUsername.setText(oldAccount.getUsername());
txtPassword.setOnAction(e -> onAccept());
}
@FXML
private void onAccept() {
String password = txtPassword.getText();
progressBar.setVisible(true);
lblCreationWarning.setText("");
Task.supplyAsync(() -> oldAccount.logInWithPassword(password))
.whenComplete(Schedulers.javafx(), authInfo -> {
success.accept(authInfo);
fireEvent(new DialogCloseEvent());
progressBar.setVisible(false);
}, e -> {
LOG.log(Level.INFO, "Failed to login with password: " + oldAccount, e);
if (e instanceof NoSelectedCharacterException) {
fireEvent(new DialogCloseEvent());
} else {
lblCreationWarning.setText(AddAccountPane.accountException(e));
}
progressBar.setVisible(false);
}).start();
}
@FXML
private void onCancel() {
failed.run();
fireEvent(new DialogCloseEvent());
}
}
@@ -0,0 +1,330 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import com.jfoenix.utils.JFXUtilities;
import com.jfoenix.controls.*;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ListProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.geometry.Pos;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.auth.*;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorDownloadException;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.auth.yggdrasil.GameProfile;
import org.jackhuang.hmcl.auth.yggdrasil.RemoteAuthenticationException;
import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilService;
import org.jackhuang.hmcl.game.TexturesLoader;
import org.jackhuang.hmcl.setting.Accounts;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.construct.*;
import org.jackhuang.hmcl.util.javafx.BindingMapping;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.*;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AddAccountPane extends StackPane {
@FXML private JFXTextField txtUsername;
@FXML private JFXPasswordField txtPassword;
@FXML private Label lblCreationWarning;
@FXML private Label lblPassword;
@FXML private JFXComboBox<AccountFactory<?>> cboType;
@FXML private JFXComboBox<AuthlibInjectorServer> cboServers;
@FXML private Label lblInjectorServer;
@FXML private JFXButton btnAccept;
@FXML private JFXButton btnAddServer;
@FXML private JFXButton btnManageServer;
@FXML private SpinnerPane acceptPane;
@FXML private HBox linksContainer;
private ListProperty<Hyperlink> links = new SimpleListProperty<>();;
public AddAccountPane() {
FXUtils.loadFXML(this, "/assets/fxml/account-add.fxml");
cboServers.setCellFactory(jfxListCellFactory(server -> new TwoLineListItem(server.getName(), server.getUrl())));
cboServers.setConverter(stringConverter(AuthlibInjectorServer::getName));
Bindings.bindContent(cboServers.getItems(), config().getAuthlibInjectorServers());
cboServers.getItems().addListener(onInvalidating(this::selectDefaultServer));
selectDefaultServer();
cboType.getItems().setAll(Accounts.FACTORY_OFFLINE, Accounts.FACTORY_MOJANG, Accounts.FACTORY_AUTHLIB_INJECTOR);
cboType.setConverter(stringConverter(Accounts::getLocalizedLoginTypeName));
// try selecting the preferred login type
cboType.getSelectionModel().select(
cboType.getItems().stream()
.filter(type -> Accounts.getLoginType(type).equals(config().getPreferredLoginType()))
.findFirst()
.orElse(Accounts.FACTORY_OFFLINE));
btnAddServer.visibleProperty().bind(cboServers.visibleProperty());
btnManageServer.visibleProperty().bind(cboServers.visibleProperty());
cboServers.getItems().addListener(onInvalidating(this::checkIfNoServer));
checkIfNoServer();
ReadOnlyObjectProperty<AccountFactory<?>> loginType = cboType.getSelectionModel().selectedItemProperty();
// remember the last used login type
loginType.addListener((observable, oldValue, newValue) -> config().setPreferredLoginType(Accounts.getLoginType(newValue)));
txtPassword.visibleProperty().bind(loginType.isNotEqualTo(Accounts.FACTORY_OFFLINE));
lblPassword.visibleProperty().bind(txtPassword.visibleProperty());
cboServers.visibleProperty().bind(loginType.isEqualTo(Accounts.FACTORY_AUTHLIB_INJECTOR));
lblInjectorServer.visibleProperty().bind(cboServers.visibleProperty());
txtUsername.getValidators().add(new Validator(i18n("input.email"), str -> !txtPassword.isVisible() || str.contains("@")));
btnAccept.disableProperty().bind(Bindings.createBooleanBinding(
() -> !( // consider the opposite situation: input is valid
txtUsername.validate() &&
// invisible means the field is not needed, neither should it be validated
(!txtPassword.isVisible() || txtPassword.validate()) &&
(!cboServers.isVisible() || cboServers.getSelectionModel().getSelectedItem() != null)
),
txtUsername.textProperty(),
txtPassword.textProperty(), txtPassword.visibleProperty(),
cboServers.getSelectionModel().selectedItemProperty(), cboServers.visibleProperty()));
// authlib-injector links
links.bind(BindingMapping.of(cboServers.getSelectionModel().selectedItemProperty())
.map(AddAccountPane::createHyperlinks)
.map(FXCollections::observableList));
Bindings.bindContent(linksContainer.getChildren(), links);
linksContainer.visibleProperty().bind(cboServers.visibleProperty());
}
private static final String[] ALLOWED_LINKS = { "register" };
public static List<Hyperlink> createHyperlinks(AuthlibInjectorServer server) {
if (server == null) {
return emptyList();
}
Map<String, String> links = server.getLinks();
List<Hyperlink> result = new ArrayList<>();
for (String key : ALLOWED_LINKS) {
String value = links.get(key);
if (value != null) {
Hyperlink link = new Hyperlink(i18n("account.injector.link." + key));
FXUtils.installSlowTooltip(link, value);
link.setOnAction(e -> FXUtils.openLink(value));
result.add(link);
}
}
return unmodifiableList(result);
}
/**
* Selects the first server if no server is selected.
*/
private void selectDefaultServer() {
if (!cboServers.getItems().isEmpty() && cboServers.getSelectionModel().isEmpty()) {
cboServers.getSelectionModel().select(0);
}
}
private void checkIfNoServer() {
if (cboServers.getItems().isEmpty())
cboServers.getStyleClass().setAll("jfx-combo-box-warning");
else
cboServers.getStyleClass().setAll("jfx-combo-box");
}
/**
* Gets the additional data that needs to be passed into {@link AccountFactory#create(CharacterSelector, String, String, Object)}.
*/
private Object getAuthAdditionalData() {
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
if (factory == Accounts.FACTORY_AUTHLIB_INJECTOR) {
return requireNonNull(cboServers.getSelectionModel().getSelectedItem(), "selected server cannot be null");
}
return null;
}
@FXML
private void onCreationAccept() {
if (btnAccept.isDisabled())
return;
acceptPane.showSpinner();
lblCreationWarning.setText("");
setDisable(true);
String username = txtUsername.getText();
String password = txtPassword.getText();
AccountFactory<?> factory = cboType.getSelectionModel().getSelectedItem();
Object additionalData = getAuthAdditionalData();
Task.supplyAsync(() -> factory.create(new Selector(), username, password, additionalData))
.whenComplete(Schedulers.javafx(), account -> {
int oldIndex = Accounts.getAccounts().indexOf(account);
if (oldIndex == -1) {
Accounts.getAccounts().add(account);
} else {
// adding an already-added account
// instead of discarding the new account, we first remove the existing one then add the new one
Accounts.getAccounts().remove(oldIndex);
Accounts.getAccounts().add(oldIndex, account);
}
// select the new account
Accounts.setSelectedAccount(account);
acceptPane.hideSpinner();
fireEvent(new DialogCloseEvent());
}, exception -> {
if (exception instanceof NoSelectedCharacterException) {
fireEvent(new DialogCloseEvent());
} else {
lblCreationWarning.setText(accountException(exception));
}
setDisable(false);
acceptPane.hideSpinner();
}).start();
}
@FXML
private void onCreationCancel() {
fireEvent(new DialogCloseEvent());
}
@FXML
private void onManageInjecterServers() {
fireEvent(new DialogCloseEvent());
Controllers.navigate(Controllers.getServersPage());
}
@FXML
private void onAddInjecterServer() {
Controllers.dialog(new AddAuthlibInjectorServerPane());
}
private class Selector extends BorderPane implements CharacterSelector {
private final AdvancedListBox listBox = new AdvancedListBox();
private final JFXButton cancel = new JFXButton();
private final CountDownLatch latch = new CountDownLatch(1);
private GameProfile selectedProfile = null;
public Selector() {
setStyle("-fx-padding: 8px;");
cancel.setText(i18n("button.cancel"));
StackPane.setAlignment(cancel, Pos.BOTTOM_RIGHT);
cancel.setOnMouseClicked(e -> latch.countDown());
listBox.startCategory(i18n("account.choose"));
setCenter(listBox);
HBox hbox = new HBox();
hbox.setAlignment(Pos.CENTER_RIGHT);
hbox.getChildren().add(cancel);
setBottom(hbox);
}
@Override
public GameProfile select(YggdrasilService service, List<GameProfile> profiles) throws NoSelectedCharacterException {
Platform.runLater(() -> {
for (GameProfile profile : profiles) {
ImageView portraitView = new ImageView();
portraitView.setSmooth(false);
portraitView.imageProperty().bind(TexturesLoader.fxAvatarBinding(service, profile.getId(), 32));
FXUtils.limitSize(portraitView, 32, 32);
IconedItem accountItem = new IconedItem(portraitView, profile.getName());
accountItem.setOnMouseClicked(e -> {
selectedProfile = profile;
latch.countDown();
});
listBox.add(accountItem);
}
Controllers.dialog(this);
});
try {
latch.await();
if (selectedProfile == null)
throw new NoSelectedCharacterException();
return selectedProfile;
} catch (InterruptedException ignore) {
throw new NoSelectedCharacterException();
} finally {
JFXUtilities.runInFX(() -> Selector.this.fireEvent(new DialogCloseEvent()));
}
}
}
public static String accountException(Exception exception) {
if (exception instanceof NoCharacterException) {
return i18n("account.failed.no_character");
} else if (exception instanceof ServerDisconnectException) {
return i18n("account.failed.connect_authentication_server");
} else if (exception instanceof ServerResponseMalformedException) {
return i18n("account.failed.server_response_malformed");
} else if (exception instanceof RemoteAuthenticationException) {
RemoteAuthenticationException remoteException = (RemoteAuthenticationException) exception;
String remoteMessage = remoteException.getRemoteMessage();
if ("ForbiddenOperationException".equals(remoteException.getRemoteName()) && remoteMessage != null) {
if (remoteMessage.contains("Invalid credentials"))
return i18n("account.failed.invalid_credentials");
else if (remoteMessage.contains("Invalid token"))
return i18n("account.failed.invalid_token");
else if (remoteMessage.contains("Invalid username or password"))
return i18n("account.failed.invalid_password");
}
return exception.getMessage();
} else if (exception instanceof AuthlibInjectorDownloadException) {
return i18n("account.failed.injector_download_failure");
} else if (exception instanceof CharacterDeletedException) {
return i18n("account.failed.character_deleted");
} else if (exception.getClass() == AuthenticationException.class) {
return exception.getLocalizedMessage();
} else {
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
}
}
}
@@ -0,0 +1,141 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXDialogLayout;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.task.Schedulers;
import org.jackhuang.hmcl.task.Task;
import org.jackhuang.hmcl.ui.animation.ContainerAnimations;
import org.jackhuang.hmcl.ui.animation.TransitionHandler;
import org.jackhuang.hmcl.ui.construct.DialogAware;
import org.jackhuang.hmcl.ui.construct.DialogCloseEvent;
import org.jackhuang.hmcl.ui.construct.SpinnerPane;
import org.jackhuang.hmcl.util.io.NetworkUtils;
import java.io.IOException;
import java.util.logging.Level;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
import static org.jackhuang.hmcl.util.Logging.LOG;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AddAuthlibInjectorServerPane extends StackPane implements DialogAware {
@FXML private StackPane addServerContainer;
@FXML private Label lblServerUrl;
@FXML private Label lblServerName;
@FXML private Label lblCreationWarning;
@FXML private Label lblServerWarning;
@FXML private JFXTextField txtServerUrl;
@FXML private JFXDialogLayout addServerPane;
@FXML private JFXDialogLayout confirmServerPane;
@FXML private SpinnerPane nextPane;
@FXML private JFXButton btnAddNext;
private TransitionHandler transitionHandler;
private AuthlibInjectorServer serverBeingAdded;
public AddAuthlibInjectorServerPane(String url) {
this();
txtServerUrl.setText(url);
onAddNext();
}
public AddAuthlibInjectorServerPane() {
loadFXML(this, "/assets/fxml/authlib-injector-server-add.fxml");
transitionHandler = new TransitionHandler(addServerContainer);
transitionHandler.setContent(addServerPane, ContainerAnimations.NONE.getAnimationProducer());
btnAddNext.disableProperty().bind(txtServerUrl.textProperty().isEmpty());
nextPane.hideSpinner();
}
@Override
public void onDialogShown() {
txtServerUrl.requestFocus();
}
private String resolveFetchExceptionMessage(Throwable exception) {
if (exception instanceof IOException) {
return i18n("account.failed.connect_injector_server");
} else {
return exception.getClass().getName() + ": " + exception.getLocalizedMessage();
}
}
@FXML
private void onAddCancel() {
fireEvent(new DialogCloseEvent());
}
@FXML
private void onAddNext() {
if (btnAddNext.isDisabled())
return;
lblCreationWarning.setText("");
String url = txtServerUrl.getText();
nextPane.showSpinner();
addServerPane.setDisable(true);
Task.runAsync(() -> {
serverBeingAdded = AuthlibInjectorServer.locateServer(url);
}).whenComplete(Schedulers.javafx(), exception -> {
addServerPane.setDisable(false);
nextPane.hideSpinner();
if (exception == null) {
lblServerName.setText(serverBeingAdded.getName());
lblServerUrl.setText(serverBeingAdded.getUrl());
lblServerWarning.setVisible("http".equals(NetworkUtils.toURL(serverBeingAdded.getUrl()).getProtocol()));
transitionHandler.setContent(confirmServerPane, ContainerAnimations.SWIPE_LEFT.getAnimationProducer());
} else {
LOG.log(Level.WARNING, "Failed to resolve auth server: " + url, exception);
lblCreationWarning.setText(resolveFetchExceptionMessage(exception));
}
}).start();
}
@FXML
private void onAddPrev() {
transitionHandler.setContent(addServerPane, ContainerAnimations.SWIPE_RIGHT.getAnimationProducer());
}
@FXML
private void onAddFinish() {
if (!config().getAuthlibInjectorServers().contains(serverBeingAdded)) {
config().getAuthlibInjectorServers().add(serverBeingAdded);
}
fireEvent(new DialogCloseEvent());
}
}
@@ -0,0 +1,67 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.effects.JFXDepthManager;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.SVG;
import java.util.function.Consumer;
public final class AuthlibInjectorServerItem extends BorderPane {
private final AuthlibInjectorServer server;
private final Label lblServerName = new Label();
private final Label lblServerUrl = new Label();
public AuthlibInjectorServerItem(AuthlibInjectorServer server, Consumer<AuthlibInjectorServerItem> deleteCallback) {
this.server = server;
lblServerName.setStyle("-fx-font-size: 15;");
lblServerUrl.setStyle("-fx-font-size: 10;");
VBox center = new VBox();
BorderPane.setAlignment(center, Pos.CENTER);
center.getChildren().addAll(lblServerName, lblServerUrl);
setCenter(center);
JFXButton right = new JFXButton();
right.setOnMouseClicked(e -> deleteCallback.accept(this));
right.getStyleClass().add("toggle-icon4");
BorderPane.setAlignment(right, Pos.CENTER);
right.setGraphic(SVG.close(Theme.blackFillBinding(), 15, 15));
setRight(right);
setStyle("-fx-background-radius: 2; -fx-background-color: white; -fx-padding: 8;");
JFXDepthManager.setDepth(this, 1);
lblServerName.textProperty().bind(Bindings.createStringBinding(server::getName, server));
lblServerUrl.setText(server.getUrl());
}
public AuthlibInjectorServer getServer() {
return server;
}
}
@@ -0,0 +1,77 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.account;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.auth.authlibinjector.AuthlibInjectorServer;
import org.jackhuang.hmcl.ui.Controllers;
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import static org.jackhuang.hmcl.setting.ConfigHolder.config;
import static org.jackhuang.hmcl.ui.FXUtils.loadFXML;
import static org.jackhuang.hmcl.ui.FXUtils.smoothScrolling;
import static org.jackhuang.hmcl.util.i18n.I18n.i18n;
public class AuthlibInjectorServersPage extends StackPane implements DecoratorPage {
private final ReadOnlyStringWrapper title = new ReadOnlyStringWrapper(this, "title", i18n("account.injector.manage.title"));
@FXML private ScrollPane scrollPane;
@FXML private VBox listPane;
@FXML private StackPane contentPane;
private ObservableList<AuthlibInjectorServerItem> serverItems;
public AuthlibInjectorServersPage() {
loadFXML(this, "/assets/fxml/authlib-injector-servers.fxml");
smoothScrolling(scrollPane);
serverItems = MappedObservableList.create(config().getAuthlibInjectorServers(), this::createServerItem);
Bindings.bindContent(listPane.getChildren(), serverItems);
}
private AuthlibInjectorServerItem createServerItem(AuthlibInjectorServer server) {
return new AuthlibInjectorServerItem(server,
item -> config().getAuthlibInjectorServers().remove(item.getServer()));
}
@FXML
private void onAdd() {
Controllers.dialog(new AddAuthlibInjectorServerPane());
}
public String getTitle() {
return title.get();
}
@Override
public ReadOnlyStringProperty titleProperty() {
return title.getReadOnlyProperty();
}
public void setTitle(String title) {
this.title.set(title);
}
}
@@ -1,7 +1,7 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
@@ -13,23 +13,17 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.api;
package org.jackhuang.hmcl.ui.animation;
/**
*
* @author huangyuhui
*/
public interface ILogger {
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import javafx.util.Duration;
void log(String message);
void warn(String message);
void warn(String msg, Throwable t);
void err(String msg);
void err(String msg, Throwable t);
public interface AnimationHandler {
Duration getDuration();
Pane getCurrentRoot();
Node getPreviousNode();
Node getCurrentNode();
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft!.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,19 +13,16 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.util.task;
package org.jackhuang.hmcl.ui.animation;
/**
*
* @author huangyuhui
*/
public interface ProgressProviderListener {
import javafx.animation.KeyFrame;
void setProgress(Task task, int prog, int max);
import java.util.List;
void setStatus(Task task, String sta);
public interface AnimationProducer {
void init(AnimationHandler handler);
void onProgressProviderDone(Task task);
List<KeyFrame> animate(AnimationHandler handler);
}
@@ -0,0 +1,164 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.animation;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.util.Duration;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
public enum ContainerAnimations {
NONE(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
}, c -> Collections.emptyList()),
/**
* A fade between the old and new view
*/
FADE(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(0);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 0, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getCurrentNode().opacityProperty(), 1, Interpolator.EASE_BOTH)))),
/**
* A zoom effect
*/
ZOOM_IN(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getPreviousNode().scaleXProperty(), 4, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 4, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH)))),
/**
* A zoom effect
*/
ZOOM_OUT(c -> {
c.getPreviousNode().setTranslateX(0);
c.getPreviousNode().setTranslateY(0);
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(1);
c.getCurrentNode().setTranslateX(0);
c.getCurrentNode().setTranslateY(0);
}, c ->
(Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getPreviousNode().scaleXProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 1, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 1, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getPreviousNode().scaleXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().scaleYProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().opacityProperty(), 0, Interpolator.EASE_BOTH))))),
/**
* A swipe effect
*/
SWIPE_LEFT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(c.getCurrentRoot().getWidth());
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH)))),
/**
* A swipe effect
*/
SWIPE_RIGHT(c -> {
c.getPreviousNode().setScaleX(1);
c.getPreviousNode().setScaleY(1);
c.getPreviousNode().setOpacity(0);
c.getPreviousNode().setTranslateX(0);
c.getCurrentNode().setScaleX(1);
c.getCurrentNode().setScaleY(1);
c.getCurrentNode().setOpacity(1);
c.getCurrentNode().setTranslateX(-c.getCurrentRoot().getWidth());
}, c ->
Arrays.asList(new KeyFrame(Duration.ZERO,
new KeyValue(c.getCurrentNode().translateXProperty(), -c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), 0, Interpolator.EASE_BOTH)),
new KeyFrame(c.getDuration(),
new KeyValue(c.getCurrentNode().translateXProperty(), 0, Interpolator.EASE_BOTH),
new KeyValue(c.getPreviousNode().translateXProperty(), c.getCurrentRoot().getWidth(), Interpolator.EASE_BOTH))));
private final AnimationProducer animationProducer;
ContainerAnimations(Consumer<AnimationHandler> init, Function<AnimationHandler, List<KeyFrame>> animationProducer) {
this.animationProducer = new AnimationProducer() {
@Override
public void init(AnimationHandler handler) {
init.accept(handler);
}
@Override
public List<KeyFrame> animate(AnimationHandler handler) {
return animationProducer.apply(handler);
}
};
}
public AnimationProducer getAnimationProducer() {
return animationProducer;
}
}
@@ -0,0 +1,116 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
public final class TransitionHandler implements AnimationHandler {
private final StackPane view;
private Timeline animation;
private Duration duration;
private Node previousNode, currentNode;
/**
* @param view A stack pane that contains another control that is {@link Parent}
*/
public TransitionHandler(StackPane view) {
this.view = view;
currentNode = view.getChildren().stream().findFirst().orElse(null);
// prevent content overflow
Rectangle clip = new Rectangle();
clip.widthProperty().bind(view.widthProperty());
clip.heightProperty().bind(view.heightProperty());
view.setClip(clip);
}
@Override
public Node getPreviousNode() {
return previousNode;
}
@Override
public Node getCurrentNode() {
return currentNode;
}
@Override
public StackPane getCurrentRoot() {
return view;
}
@Override
public Duration getDuration() {
return duration;
}
public void setContent(Node newView, AnimationProducer transition) {
setContent(newView, transition, Duration.millis(320));
}
public void setContent(Node newView, AnimationProducer transition, Duration duration) {
this.duration = duration;
Timeline prev = animation;
if (prev != null)
prev.stop();
updateContent(newView);
transition.init(this);
// runLater or "init" will not work
Platform.runLater(() -> {
Timeline nowAnimation = new Timeline();
nowAnimation.getKeyFrames().addAll(transition.animate(this));
nowAnimation.getKeyFrames().add(new KeyFrame(duration, e -> {
view.setMouseTransparent(false);
view.getChildren().remove(previousNode);
}));
nowAnimation.play();
animation = nowAnimation;
});
}
private void updateContent(Node newView) {
if (view.getWidth() > 0 && view.getHeight() > 0) {
previousNode = currentNode;
if (previousNode == null)
previousNode = EMPTY_PANE;
} else
previousNode = EMPTY_PANE;
if (previousNode == newView)
previousNode = EMPTY_PANE;
view.setMouseTransparent(true);
currentNode = newView;
view.getChildren().setAll(previousNode, currentNode);
}
private final StackPane EMPTY_PANE = new StackPane();
}
@@ -0,0 +1,85 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import org.jackhuang.hmcl.ui.FXUtils;
public class AdvancedListBox extends ScrollPane {
private final VBox container = new VBox();
{
setContent(container);
FXUtils.smoothScrolling(this);
setFitToHeight(true);
setFitToWidth(true);
setHbarPolicy(ScrollBarPolicy.NEVER);
container.setSpacing(5);
container.getStyleClass().add("advanced-list-box-content");
}
public AdvancedListBox add(Node child) {
if (child instanceof Pane || child instanceof AdvancedListItem)
container.getChildren().add(child);
else {
StackPane pane = new StackPane();
pane.getStyleClass().add("advanced-list-box-item");
pane.getChildren().setAll(child);
container.getChildren().add(pane);
}
return this;
}
public AdvancedListBox remove(Node child) {
if (child instanceof Pane)
container.getChildren().remove(child);
else {
StackPane pane = null;
for (Node node : container.getChildren())
if (node instanceof StackPane) {
ObservableList<Node> list = ((StackPane) node).getChildren();
if (list.size() == 1 && list.get(0) == child)
pane = (StackPane) node;
}
if (pane == null)
throw new Error();
container.getChildren().remove(pane);
}
return this;
}
public AdvancedListBox startCategory(String category) {
return add(new ClassTitle(category));
}
public void setSpacing(double spacing) {
container.setSpacing(spacing);
}
public void clear() {
container.getChildren().clear();
}
}
@@ -0,0 +1,123 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.beans.property.*;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
public class AdvancedListItem extends Control {
private final ObjectProperty<Image> image = new SimpleObjectProperty<>(this, "image");
private final ObjectProperty<Node> rightGraphic = new SimpleObjectProperty<>(this, "rightGraphic");
private final StringProperty title = new SimpleStringProperty(this, "title");
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle");
private final BooleanProperty actionButtonVisible = new SimpleBooleanProperty(this, "actionButtonVisible", true);
public AdvancedListItem() {
addEventHandler(MouseEvent.MOUSE_CLICKED, e -> fireEvent(new ActionEvent()));
}
public Image getImage() {
return image.get();
}
public ObjectProperty<Image> imageProperty() {
return image;
}
public void setImage(Image image) {
this.image.set(image);
}
public Node getRightGraphic() {
return rightGraphic.get();
}
public ObjectProperty<Node> rightGraphicProperty() {
return rightGraphic;
}
public void setRightGraphic(Node rightGraphic) {
this.rightGraphic.set(rightGraphic);
}
public String getTitle() {
return title.get();
}
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public String getSubtitle() {
return subtitle.get();
}
public StringProperty subtitleProperty() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle.set(subtitle);
}
public boolean isActionButtonVisible() {
return actionButtonVisible.get();
}
public BooleanProperty actionButtonVisibleProperty() {
return actionButtonVisible;
}
public void setActionButtonVisible(boolean actionButtonVisible) {
this.actionButtonVisible.set(actionButtonVisible);
}
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() {
return onAction;
}
public final void setOnAction(EventHandler<ActionEvent> value) {
onActionProperty().set(value);
}
public final EventHandler<ActionEvent> getOnAction() {
return onActionProperty().get();
}
private ObjectProperty<EventHandler<ActionEvent>> onAction = new SimpleObjectProperty<EventHandler<ActionEvent>>(this, "onAction") {
@Override
protected void invalidated() {
setEventHandler(ActionEvent.ACTION, get());
}
};
@Override
protected Skin<?> createDefaultSkin() {
return new AdvancedListItemSkin(this);
}
}
@@ -0,0 +1,108 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Label;
import javafx.scene.control.SkinBase;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.text.TextAlignment;
import org.jackhuang.hmcl.ui.FXUtils;
public class AdvancedListItemSkin extends SkinBase<AdvancedListItem> {
public AdvancedListItemSkin(AdvancedListItem skinnable) {
super(skinnable);
StackPane stackPane = new StackPane();
RipplerContainer container = new RipplerContainer(stackPane);
BorderPane root = new BorderPane();
root.setPickOnBounds(false);
HBox left = new HBox();
left.setAlignment(Pos.CENTER);
left.setMouseTransparent(true);
StackPane imageViewContainer = new StackPane();
FXUtils.setLimitWidth(imageViewContainer, 32);
FXUtils.setLimitHeight(imageViewContainer, 32);
ImageView imageView = new ImageView();
FXUtils.limitSize(imageView, 32, 32);
imageView.setPreserveRatio(true);
imageView.imageProperty().bind(skinnable.imageProperty());
imageViewContainer.getChildren().setAll(imageView);
VBox vbox = new VBox();
vbox.setAlignment(Pos.CENTER_LEFT);
vbox.setPadding(new Insets(0, 0, 0, 10));
Label title = new Label();
title.textProperty().bind(skinnable.titleProperty());
title.setMaxWidth(90);
title.setStyle("-fx-font-size: 15;");
title.setTextAlignment(TextAlignment.JUSTIFY);
vbox.getChildren().add(title);
Label subtitle = new Label();
subtitle.textProperty().bind(skinnable.subtitleProperty());
subtitle.setMaxWidth(90);
subtitle.setStyle("-fx-font-size: 10;");
subtitle.setTextAlignment(TextAlignment.JUSTIFY);
vbox.getChildren().add(subtitle);
FXUtils.onChangeAndOperate(skinnable.subtitleProperty(), subtitleString -> {
if (subtitleString == null) vbox.getChildren().setAll(title);
else vbox.getChildren().setAll(title, subtitle);
});
left.getChildren().setAll(imageViewContainer, vbox);
root.setLeft(left);
HBox right = new HBox();
right.setAlignment(Pos.CENTER);
right.setMouseTransparent(true);
right.getStyleClass().add("toggle-icon4");
FXUtils.setLimitWidth(right, 40);
FXUtils.onChangeAndOperate(skinnable.rightGraphicProperty(),
newGraphic -> {
if (newGraphic == null) {
right.getChildren().clear();
} else {
right.getChildren().setAll(newGraphic);
}
});
root.setRight(right);
FXUtils.onChangeAndOperate(skinnable.actionButtonVisibleProperty(),
visible -> root.setRight(visible ? right : null));
stackPane.setStyle("-fx-padding: 10 16 10 16;");
stackPane.getStyleClass().add("transparent");
stackPane.setPickOnBounds(false);
stackPane.getChildren().setAll(root);
getChildren().setAll(container);
}
}
@@ -0,0 +1,63 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.scene.Node;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import org.jackhuang.hmcl.util.Lang;
/**
* @author huangyuhui
*/
public class ClassTitle extends StackPane {
private final Node content;
public ClassTitle(String text) {
this(new Text(text));
}
public ClassTitle(Node content) {
this.content = content;
VBox vbox = new VBox();
vbox.getChildren().addAll(content);
Rectangle rectangle = new Rectangle();
rectangle.widthProperty().bind(vbox.widthProperty());
rectangle.setHeight(1.0);
rectangle.setFill(Color.GRAY);
vbox.getChildren().add(rectangle);
getChildren().setAll(vbox);
getStyleClass().add("class-title");
}
public ClassTitle(String text, Node rightNode) {
this(Lang.apply(new BorderPane(), borderPane -> {
borderPane.setLeft(Lang.apply(new VBox(), vBox -> vBox.getChildren().setAll(new Text(text))));
borderPane.setRight(rightNode);
}));
}
public Node getContent() {
return content;
}
}
@@ -0,0 +1,132 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import org.jackhuang.hmcl.util.javafx.MappedObservableList;
import javafx.beans.DefaultProperty;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.css.PseudoClass;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.SkinBase;
import javafx.scene.layout.VBox;
@DefaultProperty("content")
public class ComponentList extends Control {
private final StringProperty title = new SimpleStringProperty(this, "title", "Group");
private final StringProperty subtitle = new SimpleStringProperty(this, "subtitle", "");
private final IntegerProperty depth = new SimpleIntegerProperty(this, "depth", 0);
private boolean hasSubtitle = false;
public final ObservableList<Node> content = FXCollections.observableArrayList();
public ComponentList() {
getStyleClass().add("options-list");
}
public String getTitle() {
return title.get();
}
public StringProperty titleProperty() {
return title;
}
public void setTitle(String title) {
this.title.set(title);
}
public String getSubtitle() {
return subtitle.get();
}
public StringProperty subtitleProperty() {
return subtitle;
}
public void setSubtitle(String subtitle) {
this.subtitle.set(subtitle);
}
public int getDepth() {
return depth.get();
}
public IntegerProperty depthProperty() {
return depth;
}
public void setDepth(int depth) {
this.depth.set(depth);
}
public boolean isHasSubtitle() {
return hasSubtitle;
}
public void setHasSubtitle(boolean hasSubtitle) {
this.hasSubtitle = hasSubtitle;
}
public ObservableList<Node> getContent() {
return content;
}
@Override
protected javafx.scene.control.Skin<?> createDefaultSkin() {
return new Skin(this);
}
protected static class Skin extends SkinBase<ComponentList> {
private static final PseudoClass PSEUDO_CLASS_FIRST = PseudoClass.getPseudoClass("first");
private final ObservableList<Node> list;
private final ObjectBinding<Node> firstItem;
protected Skin(ComponentList control) {
super(control);
list = MappedObservableList.create(control.getContent(), node -> {
ComponentListCell cell = new ComponentListCell(node);
cell.getStyleClass().add("options-list-item");
return cell;
});
firstItem = Bindings.valueAt(list, 0);
firstItem.addListener((observable, oldValue, newValue) -> {
if (newValue != null)
newValue.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, true);
if (oldValue != null)
oldValue.pseudoClassStateChanged(PSEUDO_CLASS_FIRST, false);
});
if (!list.isEmpty())
list.get(0).pseudoClassStateChanged(PSEUDO_CLASS_FIRST, true);
VBox vbox = new VBox();
Bindings.bindContent(vbox.getChildren(), list);
getChildren().setAll(vbox);
}
}
}
@@ -0,0 +1,179 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import com.jfoenix.controls.JFXButton;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;
import org.jackhuang.hmcl.setting.Theme;
import org.jackhuang.hmcl.ui.FXUtils;
import org.jackhuang.hmcl.ui.SVG;
/**
* @author huangyuhui
*/
class ComponentListCell extends StackPane {
private final Node content;
private Animation expandAnimation;
private Rectangle clipRect;
private final BooleanProperty expanded = new SimpleBooleanProperty(this, "expanded", false);
ComponentListCell(Node content) {
this.content = content;
updateLayout();
}
private void updateClip(double newHeight) {
if (clipRect != null)
clipRect.setHeight(newHeight);
}
@Override
protected void layoutChildren() {
super.layoutChildren();
if (clipRect == null)
clipRect = new Rectangle(0, 0, getWidth(), getHeight());
else {
clipRect.setX(0);
clipRect.setY(0);
clipRect.setHeight(getHeight());
clipRect.setWidth(getWidth());
}
}
private void updateLayout() {
if (content instanceof ComponentList) {
ComponentList list = (ComponentList) content;
content.getStyleClass().remove("options-list");
content.getStyleClass().add("options-sublist");
BorderPane groupNode = new BorderPane();
Node expandIcon = SVG.expand(Theme.blackFillBinding(), 10, 10);
JFXButton expandButton = new JFXButton();
expandButton.setGraphic(expandIcon);
expandButton.getStyleClass().add("options-list-item-expand-button");
VBox labelVBox = new VBox();
labelVBox.setAlignment(Pos.CENTER_LEFT);
boolean overrideHeaderLeft = false;
if (list instanceof ComponentSublist) {
Node leftNode = ((ComponentSublist) list).getHeaderLeft();
if (leftNode != null) {
labelVBox.getChildren().setAll(leftNode);
overrideHeaderLeft = true;
}
}
if (!overrideHeaderLeft) {
Label label = new Label();
label.textProperty().bind(list.titleProperty());
labelVBox.getChildren().add(label);
if (list.isHasSubtitle()) {
Label subtitleLabel = new Label();
subtitleLabel.textProperty().bind(list.subtitleProperty());
subtitleLabel.getStyleClass().add("subtitle-label");
labelVBox.getChildren().add(subtitleLabel);
}
}
groupNode.setLeft(labelVBox);
HBox right = new HBox();
right.setSpacing(16);
right.setAlignment(Pos.CENTER_RIGHT);
if (list instanceof ComponentSublist) {
Node rightNode = ((ComponentSublist) list).getHeaderRight();
if (rightNode != null)
right.getChildren().add(rightNode);
}
right.getChildren().add(expandButton);
groupNode.setRight(right);
VBox container = new VBox();
container.setPadding(new Insets(8, 0, 0, 0));
FXUtils.setLimitHeight(container, 0);
FXUtils.setOverflowHidden(container, true);
container.getChildren().setAll(content);
groupNode.setBottom(container);
expandButton.setOnMouseClicked(e -> {
if (expandAnimation != null && expandAnimation.getStatus() == Animation.Status.RUNNING) {
expandAnimation.stop();
}
setExpanded(!isExpanded());
double newAnimatedHeight = content.prefHeight(-1) * (isExpanded() ? 1 : -1);
double newHeight = isExpanded() ? getHeight() + newAnimatedHeight : prefHeight(-1);
double contentHeight = isExpanded() ? newAnimatedHeight : 0;
if (isExpanded()) {
updateClip(newHeight);
}
expandAnimation = new Timeline(new KeyFrame(new Duration(320.0),
new KeyValue(container.minHeightProperty(), contentHeight, FXUtils.SINE),
new KeyValue(container.maxHeightProperty(), contentHeight, FXUtils.SINE)
));
if (!isExpanded()) {
expandAnimation.setOnFinished(e2 -> updateClip(newHeight));
}
expandAnimation.play();
});
expandedProperty().addListener((a, b, newValue) ->
expandIcon.setRotate(newValue ? 180 : 0));
getChildren().setAll(groupNode);
} else
getChildren().setAll(content);
}
public boolean isExpanded() {
return expanded.get();
}
public BooleanProperty expandedProperty() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded.set(expanded);
}
}
@@ -0,0 +1,58 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.beans.DefaultProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.Node;
@DefaultProperty("content")
public final class ComponentSublist extends ComponentList {
private final ObjectProperty<Node> headerLeft = new SimpleObjectProperty<>(this, "headerLeft");
private final ObjectProperty<Node> headerRight = new SimpleObjectProperty<>(this, "headerRight");
public ComponentSublist() {
super();
}
public Node getHeaderLeft() {
return headerLeft.get();
}
public ObjectProperty<Node> headerLeftProperty() {
return headerLeft;
}
public void setHeaderLeft(Node headerLeft) {
this.headerLeft.set(headerLeft);
}
public Node getHeaderRight() {
return headerRight.get();
}
public ObjectProperty<Node> headerRightProperty() {
return headerRight;
}
public void setHeaderRight(Node headerRight) {
this.headerRight.set(headerRight);
}
}
@@ -1,6 +1,6 @@
/*
* Hello Minecraft! Launcher.
* Copyright (C) 2013 huangyuhui <huanghongxun2008@126.com>
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -13,20 +13,19 @@
* 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/}.
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.core.service;
package org.jackhuang.hmcl.ui.construct;
import org.jackhuang.hmcl.ui.Controllers;
/**
*
* @author huangyuhui
* @author yushijinhun
* @see Controllers#dialog(javafx.scene.layout.Region)
*/
public abstract class IMinecraftBasicService {
public interface DialogAware {
public IMinecraftService service;
public IMinecraftBasicService(IMinecraftService service) {
this.service = service;
default void onDialogShown() {
}
}
@@ -0,0 +1,44 @@
/*
* Hello Minecraft! Launcher
* Copyright (C) 2019 huangyuhui <huanghongxun2008@126.com> and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.jackhuang.hmcl.ui.construct;
import javafx.event.Event;
import javafx.event.EventTarget;
import javafx.event.EventType;
import javafx.scene.layout.Region;
import org.jackhuang.hmcl.ui.Controllers;
/**
* Indicates a close operation on the dialog.
*
* @author yushijinhun
* @see Controllers#dialog(Region)
*/
public class DialogCloseEvent extends Event {
public static final EventType<DialogCloseEvent> CLOSE = new EventType<>("DIALOG_CLOSE");
public DialogCloseEvent() {
super(CLOSE);
}
public DialogCloseEvent(Object source, EventTarget target) {
super(source, target, CLOSE);
}
}
Loaded 100 of 985 files, more files were not shown because too many files have changed in this diff. Show more