mirror of
https://github.com/HMCL-dev/HMCL.git
synced 2024-12-27 07:10:41 +08:00
Java Selection
This commit is contained in:
parent
99f60ea6e5
commit
dc468f1a76
@ -47,8 +47,8 @@ class VersionSetting() {
|
||||
/**
|
||||
* Java version or null if user customizes java directory.
|
||||
*/
|
||||
val javaProperty = ImmediateNullableStringProperty(this, "java", null)
|
||||
var java: String? by javaProperty
|
||||
val javaProperty = ImmediateStringProperty(this, "java", "")
|
||||
var java: String by javaProperty
|
||||
|
||||
/**
|
||||
* User customized java directory or null if user uses system Java.
|
||||
@ -169,6 +169,27 @@ class VersionSetting() {
|
||||
val launcherVisibilityProperty = ImmediateObjectProperty<LauncherVisibility>(this, "launcherVisibility", LauncherVisibility.HIDE)
|
||||
var launcherVisibility: LauncherVisibility by launcherVisibilityProperty
|
||||
|
||||
val javaVersion: JavaVersion? get() {
|
||||
// TODO: lazy initialization may result in UI suspension.
|
||||
if (java.isBlank())
|
||||
java = if (javaDir.isBlank()) "Default" else "Custom"
|
||||
if (java == "Default") return JavaVersion.fromCurrentEnvironment()
|
||||
else if (java == "Custom") {
|
||||
try {
|
||||
return JavaVersion.fromExecutable(File(javaDir))
|
||||
} catch (e: IOException) {
|
||||
return null // Custom Java Directory not found,
|
||||
}
|
||||
} else if (java.isNotBlank()) {
|
||||
val c = JavaVersion.JAVAS[java]
|
||||
if (c == null) {
|
||||
java = "Default"
|
||||
return JavaVersion.fromCurrentEnvironment()
|
||||
} else
|
||||
return c
|
||||
} else throw Error()
|
||||
}
|
||||
|
||||
fun addPropertyChangedListener(listener: InvalidationListener) {
|
||||
usesGlobalProperty.addListener(listener)
|
||||
javaProperty.addListener(listener)
|
||||
@ -194,8 +215,7 @@ class VersionSetting() {
|
||||
fun toLaunchOptions(gameDir: File): LaunchOptions {
|
||||
return LaunchOptions(
|
||||
gameDir = gameDir,
|
||||
java = if (java == null) JavaVersion.fromCurrentEnvironment()
|
||||
else JavaVersion.fromExecutable(File(java)),
|
||||
java = javaVersion ?: JavaVersion.fromCurrentEnvironment(),
|
||||
versionName = Main.TITLE,
|
||||
profileName = Main.TITLE,
|
||||
minecraftArgs = minecraftArgs,
|
||||
@ -261,7 +281,7 @@ class VersionSetting() {
|
||||
javaDir = json["javaDir"]?.asString ?: ""
|
||||
precalledCommand = json["precalledCommand"]?.asString ?: ""
|
||||
serverIp = json["serverIp"]?.asString ?: ""
|
||||
java = json["java"]?.asString
|
||||
java = json["java"]?.asString ?: ""
|
||||
wrapper = json["wrapper"]?.asString ?: ""
|
||||
fullscreen = json["fullscreen"]?.asBoolean ?: false
|
||||
noJVMArgs = json["noJVMArgs"]?.asBoolean ?: false
|
||||
|
@ -34,10 +34,8 @@ import org.jackhuang.hmcl.auth.yggdrasil.YggdrasilAccount
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.Settings
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.task
|
||||
import org.jackhuang.hmcl.task.taskResult
|
||||
import org.jackhuang.hmcl.ui.wizard.DecoratorPage
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
class AccountsPage() : StackPane(), DecoratorPage {
|
||||
override val titleProperty: StringProperty = SimpleStringProperty(this, "title", "Accounts")
|
||||
@ -136,7 +134,7 @@ class AccountsPage() : StackPane(), DecoratorPage {
|
||||
val username = txtUsername.text
|
||||
val password = txtPassword.text
|
||||
progressBar.isVisible = true
|
||||
val task = task(Callable {
|
||||
taskResult("create_account") {
|
||||
try {
|
||||
val account = when (type) {
|
||||
0 -> OfflineAccount.fromUsername(username)
|
||||
@ -149,9 +147,8 @@ class AccountsPage() : StackPane(), DecoratorPage {
|
||||
} catch (e: Exception) {
|
||||
e
|
||||
}
|
||||
})
|
||||
task.subscribe(Scheduler.JAVAFX) {
|
||||
val account = task.result
|
||||
}.subscribe(Scheduler.JAVAFX) {
|
||||
val account: Any = it["create_account"]
|
||||
if (account is Account) {
|
||||
Settings.addAccount(account)
|
||||
dialog.close()
|
||||
|
@ -19,14 +19,11 @@ package org.jackhuang.hmcl.ui
|
||||
|
||||
import com.jfoenix.effects.JFXDepthManager
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.layout.VBox
|
||||
import org.jackhuang.hmcl.mod.ModManager
|
||||
import org.jackhuang.hmcl.task.Scheduler
|
||||
import org.jackhuang.hmcl.task.Task
|
||||
import org.jackhuang.hmcl.task.task
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
class ModController {
|
||||
@FXML lateinit var scrollPane: ScrollPane
|
||||
|
@ -19,23 +19,27 @@ package org.jackhuang.hmcl.ui
|
||||
|
||||
import com.jfoenix.controls.*
|
||||
import javafx.beans.InvalidationListener
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.beans.value.ChangeListener
|
||||
import javafx.fxml.FXML
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.Label
|
||||
import javafx.scene.control.ScrollPane
|
||||
import javafx.scene.layout.GridPane
|
||||
import javafx.scene.layout.VBox
|
||||
import javafx.scene.control.Toggle
|
||||
import javafx.scene.control.ToggleGroup
|
||||
import javafx.scene.layout.*
|
||||
import javafx.stage.DirectoryChooser
|
||||
import org.jackhuang.hmcl.i18n
|
||||
import org.jackhuang.hmcl.setting.VersionSetting
|
||||
import org.jackhuang.hmcl.ui.construct.ComponentList
|
||||
import org.jackhuang.hmcl.ui.construct.NumberValidator
|
||||
import org.jackhuang.hmcl.util.JavaVersion
|
||||
import org.jackhuang.hmcl.util.OS
|
||||
|
||||
class VersionSettingsController {
|
||||
var lastVersionSetting: VersionSetting? = null
|
||||
@FXML lateinit var rootPane: VBox
|
||||
@FXML lateinit var scroll: ScrollPane
|
||||
@FXML lateinit var settingsPane: GridPane
|
||||
@FXML lateinit var txtWidth: JFXTextField
|
||||
@FXML lateinit var txtHeight: JFXTextField
|
||||
@FXML lateinit var txtMaxMemory: JFXTextField
|
||||
@ -45,7 +49,7 @@ class VersionSettingsController {
|
||||
@FXML lateinit var txtWrapper: JFXTextField
|
||||
@FXML lateinit var txtPrecallingCommand: JFXTextField
|
||||
@FXML lateinit var txtServerIP: JFXTextField
|
||||
@FXML lateinit var txtGameDir: JFXTextField
|
||||
@FXML lateinit var txtJavaDir: JFXTextField
|
||||
@FXML lateinit var advancedSettingsPane: ComponentList
|
||||
@FXML lateinit var cboLauncherVisibility: JFXComboBox<*>
|
||||
@FXML lateinit var cboRunDirectory: JFXComboBox<*>
|
||||
@ -54,6 +58,13 @@ class VersionSettingsController {
|
||||
@FXML lateinit var chkNoJVMArgs: JFXToggleButton
|
||||
@FXML lateinit var chkNoCommon: JFXToggleButton
|
||||
@FXML lateinit var chkNoGameCheck: JFXToggleButton
|
||||
@FXML lateinit var componentJava: ComponentList
|
||||
@FXML lateinit var javaPane: VBox
|
||||
@FXML lateinit var javaPaneCustom: HBox
|
||||
@FXML lateinit var radioCustom: JFXRadioButton
|
||||
@FXML lateinit var btnJavaSelect: JFXButton
|
||||
|
||||
val javaGroup = ToggleGroup()
|
||||
|
||||
fun initialize() {
|
||||
lblPhysicalMemory.text = i18n("settings.physical_memory") + ": ${OS.TOTAL_MEMORY}MB"
|
||||
@ -61,7 +72,7 @@ class VersionSettingsController {
|
||||
scroll.smoothScrolling()
|
||||
|
||||
val limit = 300.0
|
||||
txtGameDir.limitWidth(limit)
|
||||
//txtJavaDir.limitWidth(limit)
|
||||
txtMaxMemory.limitWidth(limit)
|
||||
cboLauncherVisibility.limitWidth(limit)
|
||||
cboRunDirectory.limitWidth(limit)
|
||||
@ -82,6 +93,32 @@ class VersionSettingsController {
|
||||
txtMaxMemory.textProperty().addListener(validation(txtMaxMemory))
|
||||
txtMetaspace.setValidators(validator(true))
|
||||
txtMetaspace.textProperty().addListener(validation(txtMetaspace))
|
||||
|
||||
javaPane.children.clear()
|
||||
javaPane.children += createJavaPane(JavaVersion.fromCurrentEnvironment(), javaGroup)
|
||||
JavaVersion.JAVAS.values.forEach { javaVersion ->
|
||||
javaPane.children += createJavaPane(javaVersion, javaGroup)
|
||||
}
|
||||
javaPane.children += javaPaneCustom
|
||||
radioCustom.toggleGroup = javaGroup
|
||||
txtJavaDir.disableProperty().bind(radioCustom.selectedProperty().not())
|
||||
btnJavaSelect.disableProperty().bind(radioCustom.selectedProperty().not())
|
||||
}
|
||||
|
||||
private fun createJavaPane(java: JavaVersion, group: ToggleGroup): Pane {
|
||||
return HBox().apply {
|
||||
style = "-fx-padding: 3;"
|
||||
spacing = 8.0
|
||||
alignment = Pos.CENTER_LEFT
|
||||
children += JFXRadioButton(java.longVersion).apply {
|
||||
toggleGroup = group
|
||||
userData = java
|
||||
}
|
||||
children += Label(java.binary.absolutePath).apply {
|
||||
styleClass += "subtitle-label"
|
||||
style += "-fx-font-size: 10;"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadVersionSetting(version: VersionSetting) {
|
||||
@ -100,6 +137,7 @@ class VersionSettingsController {
|
||||
fullscreenProperty.unbind()
|
||||
notCheckGameProperty.unbind()
|
||||
noCommonProperty.unbind()
|
||||
javaDirProperty.unbind()
|
||||
unbindEnum(cboLauncherVisibility)
|
||||
unbindEnum(cboRunDirectory)
|
||||
}
|
||||
@ -107,6 +145,7 @@ class VersionSettingsController {
|
||||
bindInt(txtWidth, version.widthProperty)
|
||||
bindInt(txtHeight, version.heightProperty)
|
||||
bindInt(txtMaxMemory, version.maxMemoryProperty)
|
||||
bindString(txtJavaDir, version.javaDirProperty)
|
||||
bindString(txtJVMArgs, version.javaArgsProperty)
|
||||
bindString(txtGameArgs, version.minecraftArgsProperty)
|
||||
bindString(txtMetaspace, version.permSizeProperty)
|
||||
@ -119,9 +158,47 @@ class VersionSettingsController {
|
||||
bindBoolean(chkNoGameCheck, version.notCheckGameProperty)
|
||||
bindBoolean(chkNoCommon, version.noCommonProperty)
|
||||
|
||||
val javaGroupKey = "java_group.listener"
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if (javaGroup.properties.containsKey(javaGroupKey))
|
||||
javaGroup.selectedToggleProperty().removeListener(javaGroup.properties[javaGroupKey] as ChangeListener<in Toggle>)
|
||||
|
||||
var flag = false
|
||||
var defaultToggle: JFXRadioButton? = null
|
||||
for (toggle in javaGroup.toggles)
|
||||
if (toggle is JFXRadioButton)
|
||||
if (toggle.userData == version.javaVersion) {
|
||||
toggle.isSelected = true
|
||||
flag = true
|
||||
} else if (toggle.userData == JavaVersion.fromCurrentEnvironment()) {
|
||||
defaultToggle = toggle
|
||||
}
|
||||
|
||||
val listener = ChangeListener<Toggle> { _, _, newValue ->
|
||||
if (newValue == radioCustom) { // Custom
|
||||
version.java = "Custom"
|
||||
} else {
|
||||
version.java = ((newValue as JFXRadioButton).userData as JavaVersion).longVersion
|
||||
}
|
||||
}
|
||||
javaGroup.properties[javaGroupKey] = listener
|
||||
javaGroup.selectedToggleProperty().addListener(listener)
|
||||
|
||||
if (!flag) {
|
||||
defaultToggle?.isSelected = true
|
||||
}
|
||||
|
||||
version.javaDirProperty.setChangedListener { initJavaSubtitle(version) }
|
||||
version.javaProperty.setChangedListener { initJavaSubtitle(version)}
|
||||
initJavaSubtitle(version)
|
||||
|
||||
lastVersionSetting = version
|
||||
}
|
||||
|
||||
private fun initJavaSubtitle(version: VersionSetting) {
|
||||
componentJava.subtitle = version.javaVersion?.binary?.absolutePath ?: "Invalid Java Directory"
|
||||
}
|
||||
|
||||
fun onShowAdvanced() {
|
||||
if (!rootPane.children.contains(advancedSettingsPane))
|
||||
rootPane.children += advancedSettingsPane
|
||||
@ -134,6 +211,6 @@ class VersionSettingsController {
|
||||
chooser.title = i18n("settings.choose_javapath")
|
||||
val selectedDir = chooser.showDialog(Controllers.stage)
|
||||
if (selectedDir != null)
|
||||
txtGameDir.text = selectedDir.absolutePath
|
||||
txtJavaDir.text = selectedDir.absolutePath
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ package org.jackhuang.hmcl.ui.construct
|
||||
import javafx.beans.DefaultProperty
|
||||
import javafx.beans.property.SimpleIntegerProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.property.SimpleStringProperty
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.collections.ListChangeListener
|
||||
import javafx.collections.ObservableList
|
||||
@ -52,10 +53,12 @@ class ComponentList: StackPane() {
|
||||
}
|
||||
|
||||
fun addChildren(node: Node) {
|
||||
if (node is ComponentList)
|
||||
if (node is ComponentList) {
|
||||
node.properties["title"] = node.title
|
||||
node.properties["subtitle"] = node.subtitle
|
||||
}
|
||||
vbox.children += StackPane().apply {
|
||||
children += ComponentListCell(this@ComponentList, node)
|
||||
children += ComponentListCell(node)
|
||||
if (vbox.children.isEmpty())
|
||||
styleClass += "options-list-item-ahead"
|
||||
else {
|
||||
@ -64,9 +67,14 @@ class ComponentList: StackPane() {
|
||||
}
|
||||
}
|
||||
|
||||
val titleProperty = SimpleObjectProperty(this, "title", "Group")
|
||||
val titleProperty = SimpleStringProperty(this, "title", "Group")
|
||||
var title: String by titleProperty
|
||||
|
||||
val subtitleProperty = SimpleStringProperty(this, "subtitle", "")
|
||||
var subtitle: String by subtitleProperty
|
||||
|
||||
var hasSubtitle: Boolean = false
|
||||
|
||||
val depthProperty = SimpleIntegerProperty(this, "depth", 0)
|
||||
var depth: Int by depthProperty
|
||||
}
|
@ -35,7 +35,7 @@ import org.jackhuang.hmcl.ui.SVG
|
||||
import org.jackhuang.hmcl.ui.limitHeight
|
||||
import org.jackhuang.hmcl.util.*
|
||||
|
||||
class ComponentListCell(private val superList: ComponentList, private val content: Node) : StackPane() {
|
||||
class ComponentListCell(private val content: Node) : StackPane() {
|
||||
|
||||
var expandAnimation: Animation? = null
|
||||
private var clipRect: Rectangle? = null
|
||||
@ -67,9 +67,7 @@ class ComponentListCell(private val superList: ComponentList, private val conten
|
||||
}
|
||||
|
||||
private fun updateLayout() {
|
||||
val isSubList = content is ComponentList
|
||||
|
||||
if (isSubList) {
|
||||
if (content is ComponentList) {
|
||||
content.styleClass -= "options-list"
|
||||
content.styleClass += "options-sublist"
|
||||
|
||||
@ -82,12 +80,33 @@ class ComponentListCell(private val superList: ComponentList, private val conten
|
||||
expandButton.styleClass += "options-list-item-expand-button"
|
||||
StackPane.setAlignment(expandButton, Pos.CENTER_RIGHT)
|
||||
|
||||
val labelVBox = VBox()
|
||||
Label().apply {
|
||||
textProperty().bind(content.titleProperty)
|
||||
isMouseTransparent = true
|
||||
labelVBox.children += this
|
||||
}
|
||||
|
||||
if (content.hasSubtitle)
|
||||
Label().apply {
|
||||
textProperty().bind(content.subtitleProperty)
|
||||
isMouseTransparent = true
|
||||
styleClass += "subtitle-label"
|
||||
labelVBox.children += this
|
||||
}
|
||||
|
||||
StackPane.setAlignment(labelVBox, Pos.CENTER_LEFT)
|
||||
groupNode.children.setAll(
|
||||
Label(content.properties["title"]?.toString() ?: "Group").apply { isMouseTransparent = true; StackPane.setAlignment(this, Pos.CENTER_LEFT) },
|
||||
labelVBox,
|
||||
expandButton)
|
||||
|
||||
val container = VBox().apply {
|
||||
style += "-fx-padding: 8 0 0 0;"
|
||||
limitHeight(0.0)
|
||||
val clipRect = Rectangle()
|
||||
clipRect.widthProperty().bind(widthProperty())
|
||||
clipRect.heightProperty().bind(heightProperty())
|
||||
clip = clipRect
|
||||
children.setAll(content)
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ class FileItem : BorderPane() {
|
||||
init {
|
||||
left = VBox().apply {
|
||||
children += Label().apply { textProperty().bind(nameProperty) }
|
||||
children += x.apply { style += "-fx-text-fill: gray;" }
|
||||
children += x.apply { styleClass += "subtitle-label" }
|
||||
}
|
||||
|
||||
right = JFXButton().apply {
|
||||
|
@ -18,9 +18,7 @@
|
||||
package org.jackhuang.hmcl.ui.download
|
||||
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.layout.Pane
|
||||
import org.jackhuang.hmcl.download.BMCLAPIDownloadProvider
|
||||
import org.jackhuang.hmcl.mod.CurseForgeModpackCompletionTask
|
||||
import org.jackhuang.hmcl.mod.CurseForgeModpackInstallTask
|
||||
import org.jackhuang.hmcl.mod.CurseForgeModpackManifest
|
||||
import org.jackhuang.hmcl.setting.EnumGameDirectory
|
||||
|
@ -23,6 +23,10 @@
|
||||
-fx-text-fill: rgba(0.0, 0.0, 0.0, 0.87);
|
||||
}
|
||||
|
||||
.subtitle-label {
|
||||
-fx-text-fill: gray;
|
||||
}
|
||||
|
||||
.radio-button-title-label {
|
||||
-fx-font-size: 16.0px;
|
||||
-fx-padding: 14.0 0.0 -20.0 0.0;
|
||||
@ -579,7 +583,11 @@
|
||||
|
||||
.options-list {
|
||||
-fx-background-color: transparent;
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 10, 0.12, -1, 2);
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);
|
||||
}
|
||||
|
||||
.card {
|
||||
-fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.26), 5, 0.06, -0.5, 1);
|
||||
}
|
||||
|
||||
.options-sublist {
|
||||
@ -602,7 +610,7 @@
|
||||
}
|
||||
|
||||
.options-list-item-expand-button {
|
||||
-fx-toggle-icon4-size: 15px;
|
||||
-fx-toggle-icon4-size: 20px;
|
||||
-fx-pref-width: -fx-toggle-icon4-size;
|
||||
-fx-max-width: -fx-toggle-icon4-size;
|
||||
-fx-min-width: -fx-toggle-icon4-size;
|
||||
|
@ -1,8 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import java.lang.*?>
|
||||
<?import java.util.*?>
|
||||
<?import javafx.scene.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
|
||||
@ -12,9 +9,9 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import com.jfoenix.controls.JFXComboBox?>
|
||||
<?import javafx.collections.FXCollections?>
|
||||
<?import com.jfoenix.controls.JFXListView?>
|
||||
<?import org.jackhuang.hmcl.ui.construct.ComponentList?>
|
||||
<?import com.jfoenix.controls.JFXToggleButton?>
|
||||
<?import com.jfoenix.controls.JFXRadioButton?>
|
||||
<StackPane xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.jackhuang.hmcl.ui.VersionSettingsController">
|
||||
@ -24,19 +21,22 @@
|
||||
<VBox fx:id="rootPane" style="-fx-padding: 20;">
|
||||
<ComponentList depth="1">
|
||||
|
||||
<ComponentList title="%settings.java_dir"> <!-- Java Directory -->
|
||||
<VBox>
|
||||
<JFXTextField fx:id="txtGameDir" BorderPane.alignment="CENTER_RIGHT" />
|
||||
<JFXButton GridPane.rowIndex="0" GridPane.columnIndex="2" onMouseClicked="#onExploreJavaDir">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/folder-open.fxml" />
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
<ComponentList fx:id="componentJava" title="%settings.java_dir" hasSubtitle="true"> <!-- Java Directory -->
|
||||
<VBox fx:id="javaPane" spacing="8">
|
||||
<HBox fx:id="javaPaneCustom" style="-fx-padding: 3;" spacing="3" alignment="CENTER_LEFT">
|
||||
<JFXRadioButton fx:id="radioCustom" text="%settings.custom" />
|
||||
<JFXTextField fx:id="txtJavaDir" BorderPane.alignment="CENTER_RIGHT" />
|
||||
<JFXButton fx:id="btnJavaSelect" onMouseClicked="#onExploreJavaDir">
|
||||
<graphic>
|
||||
<fx:include source="/assets/svg/folder-open.fxml" />
|
||||
</graphic>
|
||||
</JFXButton>
|
||||
</HBox>
|
||||
</VBox>
|
||||
</ComponentList>
|
||||
|
||||
<BorderPane> <!-- Max Memory -->
|
||||
<left><VBox><Label text="%settings.max_memory" BorderPane.alignment="CENTER_LEFT" /><Label fx:id="lblPhysicalMemory" style="-fx-text-fill: gray;" /></VBox></left>
|
||||
<left><VBox><Label text="%settings.max_memory" BorderPane.alignment="CENTER_LEFT" /><Label fx:id="lblPhysicalMemory" styleClass="subtitle-label" /></VBox></left>
|
||||
<right><JFXTextField fx:id="txtMaxMemory" BorderPane.alignment="CENTER_RIGHT" /></right>
|
||||
</BorderPane>
|
||||
|
||||
@ -73,16 +73,16 @@
|
||||
<BorderPane> <!-- Dimension -->
|
||||
<left><Label text="%settings.dimension" BorderPane.alignment="CENTER_LEFT" /></left>
|
||||
<right>
|
||||
<BorderPane BorderPane.alignment="CENTER_RIGHT">
|
||||
<BorderPane>
|
||||
<left>
|
||||
<HBox prefWidth="210">
|
||||
<HBox prefWidth="210" spacing="3" alignment="CENTER" BorderPane.alignment="CENTER">
|
||||
<JFXTextField fx:id="txtWidth" promptText="800" prefWidth="100" />
|
||||
<Label>x</Label>
|
||||
<JFXTextField fx:id="txtHeight" promptText="480" prefWidth="100" />
|
||||
</HBox>
|
||||
</left>
|
||||
<right>
|
||||
<JFXCheckBox fx:id="chkFullscreen" text="%settings.fullscreen" alignment="CENTER">
|
||||
<JFXCheckBox fx:id="chkFullscreen" text="%settings.fullscreen" alignment="CENTER" BorderPane.alignment="CENTER">
|
||||
<BorderPane.margin>
|
||||
<Insets right="7"/>
|
||||
</BorderPane.margin>
|
||||
@ -101,11 +101,14 @@
|
||||
<Tooltip text="%advancedsettings.java_args_default" />
|
||||
</tooltip>
|
||||
</JFXTextField>
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.Minecraft_arguments" styleClass="fit-width" fx:id="txtGameArgs" />
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.java_permanent_generation_space" styleClass="fit-width" fx:id="txtMetaspace" />
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.wrapper_launcher" styleClass="fit-width" fx:id="txtWrapper" />
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.precall_command" styleClass="fit-width" fx:id="txtPrecallingCommand" />
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.server_ip" styleClass="fit-width" fx:id="txtServerIP" />
|
||||
<fx:define>
|
||||
<Insets fx:id="insets" bottom="8" />
|
||||
</fx:define>
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.Minecraft_arguments" styleClass="fit-width" fx:id="txtGameArgs" StackPane.margin="$insets" />
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.java_permanent_generation_space" styleClass="fit-width" fx:id="txtMetaspace" StackPane.margin="$insets" />
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.wrapper_launcher" styleClass="fit-width" fx:id="txtWrapper" StackPane.margin="$insets" />
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.precall_command" styleClass="fit-width" fx:id="txtPrecallingCommand" StackPane.margin="$insets" />
|
||||
<JFXTextField labelFloat="true" promptText="%advancedsettings.server_ip" styleClass="fit-width" fx:id="txtServerIP" StackPane.margin="$insets" />
|
||||
<BorderPane><left><Label text="%advancedsettings.no_jvm_args" /></left><right><JFXToggleButton fx:id="chkNoJVMArgs" size="7" /></right></BorderPane>
|
||||
<BorderPane><left><Label text="%advancedsettings.no_common" /></left><right><JFXToggleButton fx:id="chkNoCommon" size="7" /></right></BorderPane>
|
||||
<BorderPane><left><Label text="%advancedsettings.dont_check_game_completeness" /></left><right><JFXToggleButton fx:id="chkNoGameCheck" size="7" /></right></BorderPane>
|
||||
|
@ -36,8 +36,9 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
private val forgeVersionList = dependencyManager.getVersionList("forge")
|
||||
private val installer: File = File("forge-installer.jar").absoluteFile
|
||||
lateinit var remote: RemoteVersion<*>
|
||||
override val dependents: MutableCollection<Task> = mutableListOf()
|
||||
override val dependencies: MutableCollection<Task> = mutableListOf()
|
||||
override val dependents = mutableListOf<Task>()
|
||||
override val dependencies = mutableListOf<Task>()
|
||||
override val id = ID
|
||||
|
||||
init {
|
||||
if (!forgeVersionList.loaded)
|
||||
@ -76,5 +77,7 @@ class ForgeInstallTask(private val dependencyManager: DefaultDependencyManager,
|
||||
check(installer.delete(), { "Unable to delete installer file $installer" })
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
const val ID = "forge_install_task"
|
||||
}
|
||||
}
|
@ -37,7 +37,7 @@ import java.util.logging.Level
|
||||
* @param resolvedVersion the <b>resolved</b> version
|
||||
*/
|
||||
class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager, private val resolvedVersion: Version): Task() {
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override val dependencies = LinkedList<Task>()
|
||||
override fun execute() {
|
||||
for (library in resolvedVersion.libraries)
|
||||
if (library.appliesToCurrentEnvironment) {
|
||||
@ -50,7 +50,7 @@ class GameLibrariesTask(private val dependencyManager: DefaultDependencyManager,
|
||||
}
|
||||
|
||||
class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override val dependencies = LinkedList<Task>()
|
||||
override fun execute() {
|
||||
val logging = version.logging?.get(DownloadType.CLIENT) ?: return
|
||||
val file = dependencyManager.repository.getLoggingObject(version.id, version.actualAssetIndex.id, logging)
|
||||
@ -60,7 +60,7 @@ class GameLoggingDownloadTask(private val dependencyManager: DefaultDependencyMa
|
||||
}
|
||||
|
||||
class GameAssetIndexDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override val dependencies = LinkedList<Task>()
|
||||
override fun execute() {
|
||||
val assetIndexInfo = version.actualAssetIndex
|
||||
val assetDir = dependencyManager.repository.getAssetDirectory(version.id, assetIndexInfo.id)
|
||||
@ -76,7 +76,8 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
|
||||
private val assetIndexTask = GameAssetIndexDownloadTask(dependencyManager, version)
|
||||
private val assetIndexInfo = version.actualAssetIndex
|
||||
private val assetIndexFile = dependencyManager.repository.getIndexFile(version.id, assetIndexInfo.id)
|
||||
override val dependents: MutableCollection<Task> = LinkedList()
|
||||
override val dependents = LinkedList<Task>()
|
||||
override val id = ID
|
||||
|
||||
init {
|
||||
if (!assetIndexFile.exists())
|
||||
@ -93,12 +94,16 @@ class GameAssetRefreshTask(private val dependencyManager: DefaultDependencyManag
|
||||
}
|
||||
result = res
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ID = "game_asset_refresh_task"
|
||||
}
|
||||
}
|
||||
|
||||
class GameAssetDownloadTask(private val dependencyManager: DefaultDependencyManager, private val version: Version) : Task() {
|
||||
private val refreshTask = GameAssetRefreshTask(dependencyManager, version)
|
||||
override val dependents: Collection<Task> = listOf(refreshTask)
|
||||
override val dependencies: MutableCollection<Task> = LinkedList()
|
||||
override val dependents = listOf(refreshTask)
|
||||
override val dependencies = LinkedList<Task>()
|
||||
override fun execute() {
|
||||
val size = refreshTask.result?.size ?: 0
|
||||
refreshTask.result?.forEach single@{ (file, assetObject) ->
|
||||
|
@ -35,8 +35,9 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
|
||||
private val remoteVersion: String): TaskResult<Version>() {
|
||||
private val liteLoaderVersionList = dependencyManager.getVersionList("liteloader") as LiteLoaderVersionList
|
||||
lateinit var remote: RemoteVersion<LiteLoaderRemoteVersionTag>
|
||||
override val dependents: MutableCollection<Task> = mutableListOf()
|
||||
override val dependencies: MutableCollection<Task> = mutableListOf()
|
||||
override val dependents = mutableListOf<Task>()
|
||||
override val dependencies = mutableListOf<Task>()
|
||||
override val id = ID
|
||||
|
||||
init {
|
||||
if (!liteLoaderVersionList.loaded)
|
||||
@ -70,4 +71,7 @@ class LiteLoaderInstallTask(private val dependencyManager: DefaultDependencyMana
|
||||
dependencies += GameLibrariesTask(dependencyManager, tempVersion)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID = "lite_loader_install_task"
|
||||
}
|
||||
}
|
@ -32,8 +32,9 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
|
||||
private val remoteVersion: String): TaskResult<Version>() {
|
||||
private val optiFineVersionList = dependencyManager.getVersionList("optifine")
|
||||
lateinit var remote: RemoteVersion<*>
|
||||
override val dependents: MutableCollection<Task> = mutableListOf()
|
||||
override val dependencies: MutableCollection<Task> = mutableListOf()
|
||||
override val dependents = mutableListOf<Task>()
|
||||
override val dependencies = mutableListOf<Task>()
|
||||
override val id = ID
|
||||
|
||||
init {
|
||||
if (!optiFineVersionList.loaded)
|
||||
@ -45,6 +46,7 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
|
||||
remote = optiFineVersionList.getVersion(gameVersion, remoteVersion) ?: throw IllegalArgumentException("Remote OptiFine version $gameVersion-$remoteVersion not found")
|
||||
}
|
||||
}
|
||||
|
||||
override fun execute() {
|
||||
val library = Library(
|
||||
groupId = "net.optifine",
|
||||
@ -73,4 +75,8 @@ class OptiFineInstallTask(private val dependencyManager: DefaultDependencyManage
|
||||
result = version.copy(libraries = merge(version.libraries, libraries), mainClass = mainClass, minecraftArguments = arg)
|
||||
dependencies += GameLibrariesTask(dependencyManager, version.copy(libraries = libraries))
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID = "optifine_install_task"
|
||||
}
|
||||
}
|
@ -68,7 +68,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
|
||||
if (OS.CURRENT_OS == OS.OSX) {
|
||||
res.add("-Xdock:name=Minecraft ${version.id}")
|
||||
res.add("-Xdock:icon=" + repository.getAssetObject(version.id, version.actualAssetIndex.id, "icons/minecraft.icns").absolutePath);
|
||||
res.add("-Xdock:icon=" + repository.getAssetObject(version.id, version.actualAssetIndex.id, "icons/minecraft.icns").absolutePath)
|
||||
}
|
||||
|
||||
val logging = version.logging
|
||||
@ -107,8 +107,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
if (options.minMemory != null && options.minMemory > 0)
|
||||
res.add("-Xms${options.minMemory}m")
|
||||
|
||||
res.add("-Dfml.ignoreInvalidMinecraftCertificates=true");
|
||||
res.add("-Dfml.ignorePatchDiscrepancies=true");
|
||||
res.add("-Dfml.ignoreInvalidMinecraftCertificates=true")
|
||||
res.add("-Dfml.ignorePatchDiscrepancies=true")
|
||||
}
|
||||
|
||||
// Classpath
|
||||
@ -161,9 +161,9 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
|
||||
// Optional Minecraft arguments
|
||||
if (options.height != null && options.width != null) {
|
||||
res.add("--height");
|
||||
res.add("--height")
|
||||
res.add(options.height.toString())
|
||||
res.add("--width");
|
||||
res.add("--width")
|
||||
res.add(options.width.toString())
|
||||
}
|
||||
|
||||
@ -180,16 +180,16 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
|
||||
if (options.proxyHost != null && options.proxyHost.isNotBlank() &&
|
||||
options.proxyPort != null && options.proxyPort.isNotBlank()) {
|
||||
res.add("--proxyHost");
|
||||
res.add("--proxyHost")
|
||||
res.add(options.proxyHost)
|
||||
res.add("--proxyPort");
|
||||
res.add("--proxyPort")
|
||||
res.add(options.proxyPort)
|
||||
if (options.proxyUser != null && options.proxyUser.isNotBlank() &&
|
||||
options.proxyPass != null && options.proxyPass.isNotBlank()) {
|
||||
res.add("--proxyUser");
|
||||
res.add(options.proxyUser);
|
||||
res.add("--proxyPass");
|
||||
res.add(options.proxyPass);
|
||||
res.add("--proxyUser")
|
||||
res.add(options.proxyUser)
|
||||
res.add("--proxyPass")
|
||||
res.add(options.proxyPass)
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,6 +246,7 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
|
||||
fun launchAsync(): TaskResult<JavaProcess> {
|
||||
return object : TaskResult<JavaProcess>() {
|
||||
override val id = LAUNCH_ASYNC_ID
|
||||
override fun execute() {
|
||||
result = launch()
|
||||
}
|
||||
@ -259,11 +260,11 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
throw IOException("Script file: $scriptFile cannot be created.")
|
||||
scriptFile.bufferedWriter().use { writer ->
|
||||
if (isWindows) {
|
||||
writer.write("@echo off");
|
||||
writer.write("@echo off")
|
||||
writer.newLine()
|
||||
writer.write("set APPDATA=" + options.gameDir.parent);
|
||||
writer.write("set APPDATA=" + options.gameDir.parent)
|
||||
writer.newLine()
|
||||
writer.write("cd /D %APPDATA%");
|
||||
writer.write("cd /D %APPDATA%")
|
||||
writer.newLine()
|
||||
}
|
||||
if (options.precalledCommand != null && options.precalledCommand.isNotBlank()) {
|
||||
@ -287,4 +288,8 @@ open class DefaultLauncher(repository: GameRepository, versionId: String, accoun
|
||||
thread(name = "stderr-pump", isDaemon = isDaemon, block = StreamPump(javaProcess.process.errorStream, processListener::onErrorLog)::run)
|
||||
thread(name = "exit-waiter", isDaemon = isDaemon, block = ExitWaiter(javaProcess.process, processListener::onExit)::run)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LAUNCH_ASYNC_ID = "process"
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ import java.nio.charset.Charset
|
||||
|
||||
class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Charsets.UTF_8, private val retry: Int = 5, private val proxy: Proxy = Proxy.NO_PROXY): TaskResult<String>() {
|
||||
override val scheduler: Scheduler = Scheduler.IO_THREAD
|
||||
override val id = ID
|
||||
|
||||
override fun execute() {
|
||||
var exception: IOException? = null
|
||||
@ -63,4 +64,8 @@ class GetTask @JvmOverloads constructor(val url: URL, val encoding: Charset = Ch
|
||||
if (exception != null)
|
||||
throw exception
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val ID = "http_get"
|
||||
}
|
||||
}
|
@ -17,8 +17,10 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
internal class SimpleTask @JvmOverloads constructor(private val runnable: () -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() {
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap
|
||||
|
||||
internal class SimpleTask @JvmOverloads constructor(private val runnable: (AutoTypingMap<String>) -> Unit, override val scheduler: Scheduler = Scheduler.DEFAULT) : Task() {
|
||||
override fun execute() {
|
||||
runnable()
|
||||
runnable(variables!!)
|
||||
}
|
||||
}
|
@ -49,6 +49,8 @@ abstract class Task {
|
||||
|
||||
var title: String = this.javaClass.toString()
|
||||
|
||||
var variables: AutoTypingMap<String>? = null
|
||||
|
||||
/**
|
||||
* @see Thread.isInterrupted
|
||||
* @throws InterruptedException if current thread is interrupted
|
||||
@ -119,12 +121,13 @@ abstract class Task {
|
||||
submit(subscriber).start()
|
||||
}
|
||||
|
||||
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit) = subscribe(task(scheduler, closure))
|
||||
fun subscribe(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit) = subscribe(task(scheduler, closure))
|
||||
|
||||
override fun toString(): String {
|
||||
return title
|
||||
}
|
||||
}
|
||||
|
||||
fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: () -> Unit): Task = SimpleTask(closure, scheduler)
|
||||
fun <V> task(callable: Callable<V>): TaskResult<V> = TaskCallable(callable)
|
||||
fun task(scheduler: Scheduler = Scheduler.DEFAULT, closure: (AutoTypingMap<String>) -> Unit): Task = SimpleTask(closure, scheduler)
|
||||
fun <V> taskResult(id: String, callable: Callable<V>): TaskResult<V> = TaskCallable(id, callable)
|
||||
fun <V> taskResult(id: String, callable: (AutoTypingMap<String>) -> V): TaskResult<V> = TaskCallable2(id, callable)
|
@ -17,10 +17,17 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap
|
||||
import java.util.concurrent.Callable
|
||||
|
||||
internal class TaskCallable<V>(private val callable: Callable<V>) : TaskResult<V>() {
|
||||
internal class TaskCallable<V>(override val id: String, private val callable: Callable<V>) : TaskResult<V>() {
|
||||
override fun execute() {
|
||||
result = callable.call()
|
||||
}
|
||||
}
|
||||
|
||||
internal class TaskCallable2<V>(override val id: String, private val callable: (AutoTypingMap<String>) -> V) : TaskResult<V>() {
|
||||
override fun execute() {
|
||||
result = callable(variables!!)
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
package org.jackhuang.hmcl.task
|
||||
|
||||
import org.jackhuang.hmcl.util.AutoTypingMap
|
||||
import org.jackhuang.hmcl.util.LOG
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@ -30,6 +31,7 @@ class TaskExecutor() {
|
||||
var canceled = false
|
||||
private set
|
||||
val totTask = AtomicInteger(0)
|
||||
val variables = AutoTypingMap<String>(mutableMapOf())
|
||||
private val taskQueue = ConcurrentLinkedQueue<Task>()
|
||||
private val workerQueue = ConcurrentLinkedQueue<Future<*>>()
|
||||
|
||||
@ -119,7 +121,10 @@ class TaskExecutor() {
|
||||
if (!doDependentsSucceeded && t.reliant || canceled)
|
||||
throw SilentException()
|
||||
|
||||
t.variables = variables
|
||||
t.execute()
|
||||
if (t is TaskResult<*>)
|
||||
variables[t.id] = t.result
|
||||
flag = true
|
||||
if (!t.hidden)
|
||||
LOG.finer("Task finished: ${t.title}")
|
||||
@ -142,6 +147,8 @@ class TaskExecutor() {
|
||||
t.onDone(TaskEvent(source = this, task = t, failed = true))
|
||||
taskListener?.onFailed(t)
|
||||
}
|
||||
} finally {
|
||||
t.variables = null
|
||||
}
|
||||
return flag
|
||||
}
|
||||
|
@ -19,4 +19,9 @@ package org.jackhuang.hmcl.task
|
||||
|
||||
abstract class TaskResult<V> : Task() {
|
||||
open var result: V? = null
|
||||
|
||||
/**
|
||||
* The task id, will be stored as key of [variables]
|
||||
*/
|
||||
abstract val id: String
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Hello Minecraft! Launcher.
|
||||
* Copyright (C) 2017 huangyuhui <huanghongxun2008@126.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see {http://www.gnu.org/licenses/}.
|
||||
*/
|
||||
package org.jackhuang.hmcl.util
|
||||
|
||||
class AutoTypingMap<K>(private val impl: MutableMap<K, Any>) {
|
||||
|
||||
fun clear() = impl.clear()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
operator fun <V> get(key: K): V = impl[key] as V
|
||||
operator fun set(key: K, value: Any?) {
|
||||
if (value != null)
|
||||
impl.set(key, value)
|
||||
}
|
||||
val values get() = impl.values
|
||||
val keys get() = impl.keys
|
||||
|
||||
fun containsKey(key: K) = impl.containsKey(key)
|
||||
fun remove(key: K) = impl.remove(key)
|
||||
}
|
@ -21,17 +21,22 @@ import com.google.gson.annotations.SerializedName
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
data class JavaVersion internal constructor(
|
||||
@SerializedName("location")
|
||||
val binary: File,
|
||||
val version: Int,
|
||||
val longVersion: String,
|
||||
val platform: Platform) : Serializable
|
||||
{
|
||||
val version = parseVersion(longVersion)
|
||||
|
||||
companion object {
|
||||
private val regex = Pattern.compile("java version \"(?<version>[1-9]*\\.[1-9]*\\.[0-9]*(.*?))\"")
|
||||
|
||||
val JAVAS: Map<String, JavaVersion>
|
||||
|
||||
val UNKNOWN: Int = -1
|
||||
val JAVA_5: Int = 50
|
||||
val JAVA_6: Int = 60
|
||||
@ -54,12 +59,15 @@ data class JavaVersion internal constructor(
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun fromExecutable(file: File): JavaVersion {
|
||||
var actualFile = file
|
||||
var platform = Platform.BIT_32
|
||||
var version: String? = null
|
||||
if (actualFile.nameWithoutExtension == "javaw") // javaw will not output version information
|
||||
actualFile = actualFile.absoluteFile.parentFile.resolve("java")
|
||||
try {
|
||||
val process = ProcessBuilder(file.absolutePath, "-version").start()
|
||||
val process = ProcessBuilder(actualFile.absolutePath, "-version").start()
|
||||
process.waitFor()
|
||||
process.inputStream.bufferedReader().forEachLine { line ->
|
||||
process.errorStream.bufferedReader().forEachLine { line ->
|
||||
val m = regex.matcher(line)
|
||||
if (m.find())
|
||||
version = m.group("version")
|
||||
@ -74,10 +82,26 @@ data class JavaVersion internal constructor(
|
||||
val parsedVersion = parseVersion(thisVersion)
|
||||
if (parsedVersion == UNKNOWN)
|
||||
throw IOException("Java version '$thisVersion' can not be recognized")
|
||||
return JavaVersion(file.parentFile, parsedVersion, platform)
|
||||
return JavaVersion(file.parentFile, thisVersion, platform)
|
||||
}
|
||||
|
||||
fun getJavaFile(home: File): File {
|
||||
private fun fromExecutable(file: File, version: String) =
|
||||
JavaVersion (
|
||||
binary = file,
|
||||
longVersion = version,
|
||||
platform = Platform.UNKNOWN
|
||||
)
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun fromJavaHome(home: File): JavaVersion {
|
||||
return fromExecutable(getJavaFile(home))
|
||||
}
|
||||
|
||||
private fun fromJavaHome(home: File, version: String): JavaVersion {
|
||||
return fromExecutable(getJavaFile(home), version)
|
||||
}
|
||||
|
||||
private fun getJavaFile(home: File): File {
|
||||
val path = home.resolve("bin")
|
||||
val javaw = path.resolve("javaw.exe")
|
||||
if (OS.CURRENT_OS === OS.WINDOWS && javaw.isFile)
|
||||
@ -86,11 +110,80 @@ data class JavaVersion internal constructor(
|
||||
return path.resolve("java")
|
||||
}
|
||||
|
||||
fun fromCurrentEnvironment(): JavaVersion {
|
||||
return JavaVersion(
|
||||
binary = getJavaFile(File(System.getProperty("java.home"))),
|
||||
version = parseVersion(System.getProperty("java.version")),
|
||||
platform = Platform.PLATFORM)
|
||||
private val currentJava: JavaVersion = JavaVersion(
|
||||
binary = getJavaFile(File(System.getProperty("java.home"))),
|
||||
longVersion = System.getProperty("java.version"),
|
||||
platform = Platform.PLATFORM)
|
||||
fun fromCurrentEnvironment() = currentJava
|
||||
|
||||
init {
|
||||
val temp = mutableMapOf<String, JavaVersion>()
|
||||
(when (OS.CURRENT_OS) {
|
||||
OS.WINDOWS -> queryWindows()
|
||||
OS.OSX -> queryMacintosh()
|
||||
else -> emptyList<JavaVersion>() /* Cannot detect Java in linux. */
|
||||
}).forEach { javaVersion ->
|
||||
temp.put(javaVersion.longVersion, javaVersion)
|
||||
}
|
||||
JAVAS = temp
|
||||
}
|
||||
|
||||
private fun queryMacintosh() = LinkedList<JavaVersion>().apply {
|
||||
val currentJRE = File("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home")
|
||||
if (currentJRE.exists())
|
||||
this += fromJavaHome(currentJRE)
|
||||
File("/Library/Java/JavaVirtualMachines/").listFiles()?.forEach { file ->
|
||||
this += fromJavaHome(file.resolve("Contents/Home"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryWindows() = LinkedList<JavaVersion>().apply {
|
||||
ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\") }
|
||||
ignoreException { this += queryRegisterKey("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Development Kit\\") }
|
||||
}
|
||||
|
||||
private fun queryRegisterKey(location: String) = LinkedList<JavaVersion>().apply {
|
||||
querySubFolders(location).forEach { java ->
|
||||
val s = java.count { it == '.' }
|
||||
if (s > 1) {
|
||||
val home = queryRegisterValue(java, "JavaHome")
|
||||
if (home != null)
|
||||
this += fromJavaHome(File(home))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun querySubFolders(location: String) = LinkedList<String>().apply {
|
||||
val cmd = arrayOf("cmd", "/c", "reg", "query", location)
|
||||
val process = Runtime.getRuntime().exec(cmd)
|
||||
process.waitFor()
|
||||
process.inputStream.bufferedReader().readLines().forEach { s ->
|
||||
if (s.startsWith(location) && s != location)
|
||||
this += s
|
||||
}
|
||||
}
|
||||
|
||||
private fun queryRegisterValue(location: String, name: String): String? {
|
||||
val cmd = arrayOf("cmd", "/c", "reg", "query", location, "/v", name)
|
||||
var last = false
|
||||
val process = Runtime.getRuntime().exec(cmd)
|
||||
process.waitFor()
|
||||
process.inputStream.bufferedReader().readLines().forEach { s ->
|
||||
if (s.isNotBlank()) {
|
||||
if (last && s.trim().startsWith(name)) {
|
||||
var begins = s.indexOf(name)
|
||||
if (begins > 0) {
|
||||
val s2 = s.substring(begins + name.length)
|
||||
begins = s2.indexOf("REG_SZ")
|
||||
if (begins > 0)
|
||||
return s2.substring(begins + "REG_SZ".length).trim()
|
||||
}
|
||||
}
|
||||
if (s.trim() == location)
|
||||
last = true
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
package org.jackhuang.hmcl.util.property
|
||||
|
||||
import javafx.beans.property.*
|
||||
import javafx.beans.value.ChangeListener
|
||||
import javafx.beans.value.ObservableValue
|
||||
|
||||
open class ImmediateStringProperty(bean: Any, name: String, initialValue: String): SimpleStringProperty(bean, name, initialValue) {
|
||||
@ -27,10 +28,6 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String
|
||||
super.set(newValue)
|
||||
}
|
||||
|
||||
protected fun superSet(newValue: String) {
|
||||
super.set(newValue)
|
||||
}
|
||||
|
||||
override fun bind(newObservable: ObservableValue<out String>) {
|
||||
super.get()
|
||||
super.bind(newObservable)
|
||||
@ -41,30 +38,17 @@ open class ImmediateStringProperty(bean: Any, name: String, initialValue: String
|
||||
super.unbind()
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
}
|
||||
|
||||
open class ImmediateNullableStringProperty(bean: Any, name: String, initialValue: String?): SimpleStringProperty(bean, name, initialValue) {
|
||||
|
||||
override fun set(newValue: String?) {
|
||||
super.get()
|
||||
super.set(newValue)
|
||||
private var myListener: (String) -> Unit = {}
|
||||
private val changeListener = ChangeListener<String> { _, _, newValue ->
|
||||
myListener(newValue)
|
||||
}
|
||||
|
||||
protected fun superSet(newValue: String?) {
|
||||
super.set(newValue)
|
||||
fun setChangedListener(listener: (String) -> Unit) {
|
||||
myListener = listener
|
||||
}
|
||||
|
||||
override fun bind(newObservable: ObservableValue<out String?>) {
|
||||
super.get()
|
||||
super.bind(newObservable)
|
||||
}
|
||||
|
||||
override fun unbind() {
|
||||
super.get()
|
||||
super.unbind()
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
@ -79,10 +63,6 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole
|
||||
super.set(newValue)
|
||||
}
|
||||
|
||||
protected fun superSet(newValue: Boolean) {
|
||||
super.set(newValue)
|
||||
}
|
||||
|
||||
override fun bind(rawObservable: ObservableValue<out Boolean>?) {
|
||||
super.get()
|
||||
super.bind(rawObservable)
|
||||
@ -93,6 +73,19 @@ open class ImmediateBooleanProperty(bean: Any, name: String, initialValue: Boole
|
||||
super.unbind()
|
||||
}
|
||||
|
||||
private var myListener: (Boolean) -> Unit = {}
|
||||
private val changeListener = ChangeListener<Boolean> { _, _, newValue ->
|
||||
myListener(newValue)
|
||||
}
|
||||
|
||||
fun setChangedListener(listener: (Boolean) -> Unit) {
|
||||
myListener = listener
|
||||
}
|
||||
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
@ -105,10 +98,6 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int):
|
||||
super.set(newValue)
|
||||
}
|
||||
|
||||
protected fun superSet(newValue: Int) {
|
||||
super.set(newValue)
|
||||
}
|
||||
|
||||
override fun bind(rawObservable: ObservableValue<out Number>) {
|
||||
super.get()
|
||||
super.bind(rawObservable)
|
||||
@ -119,6 +108,19 @@ open class ImmediateIntegerProperty(bean: Any, name: String, initialValue: Int):
|
||||
super.unbind()
|
||||
}
|
||||
|
||||
private var myListener: (Int) -> Unit = {}
|
||||
private val changeListener = ChangeListener<Number> { _, _, newValue ->
|
||||
myListener(newValue.toInt())
|
||||
}
|
||||
|
||||
fun setChangedListener(listener: (Int) -> Unit) {
|
||||
myListener = listener
|
||||
}
|
||||
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
@ -131,10 +133,6 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double
|
||||
super.set(newValue)
|
||||
}
|
||||
|
||||
protected fun superSet(newValue: Double) {
|
||||
super.set(newValue)
|
||||
}
|
||||
|
||||
override fun bind(rawObservable: ObservableValue<out Number>) {
|
||||
super.get()
|
||||
super.bind(rawObservable)
|
||||
@ -145,6 +143,19 @@ open class ImmediateDoubleProperty(bean: Any, name: String, initialValue: Double
|
||||
super.unbind()
|
||||
}
|
||||
|
||||
private var myListener: (Double) -> Unit = {}
|
||||
private val changeListener = ChangeListener<Number> { _, _, newValue ->
|
||||
myListener(newValue.toDouble())
|
||||
}
|
||||
|
||||
fun setChangedListener(listener: (Double) -> Unit) {
|
||||
myListener = listener
|
||||
}
|
||||
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
@ -167,6 +178,19 @@ open class ImmediateObjectProperty<T>(bean: Any, name: String, initialValue: T):
|
||||
super.unbind()
|
||||
}
|
||||
|
||||
private var myListener: (T) -> Unit = {}
|
||||
private val changeListener = ChangeListener<T> { _, _, newValue ->
|
||||
myListener(newValue)
|
||||
}
|
||||
|
||||
fun setChangedListener(listener: (T) -> Unit) {
|
||||
myListener = listener
|
||||
}
|
||||
|
||||
init {
|
||||
addListener(changeListener)
|
||||
}
|
||||
|
||||
public override fun fireValueChangedEvent() {
|
||||
super.fireValueChangedEvent()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user